diff --git a/its/ruling/src/test/expected/js/amplify/javascript-S6660.json b/its/ruling/src/test/expected/js/amplify/javascript-S6660.json new file mode 100644 index 0000000000..ab08f6bd52 --- /dev/null +++ b/its/ruling/src/test/expected/js/amplify/javascript-S6660.json @@ -0,0 +1,6 @@ +{ +'amplify:src/store.js':[ +77, +205, +], +} diff --git a/its/ruling/src/test/expected/js/angular.js/javascript-S6660.json b/its/ruling/src/test/expected/js/angular.js/javascript-S6660.json new file mode 100644 index 0000000000..996452867e --- /dev/null +++ b/its/ruling/src/test/expected/js/angular.js/javascript-S6660.json @@ -0,0 +1,36 @@ +{ +'angular.js:Gruntfile.js':[ +41, +], +'angular.js:lib/grunt/utils.js':[ +262, +], +'angular.js:src/Angular.js':[ +345, +], +'angular.js:src/jqLite.js':[ +418, +421, +], +'angular.js:src/ng/compile.js':[ +2234, +], +'angular.js:src/ng/directive/form.js':[ +646, +], +'angular.js:src/ng/directive/ngModelOptions.js':[ +56, +], +'angular.js:src/ng/filter/limitTo.js':[ +120, +], +'angular.js:src/ng/http.js':[ +1394, +], +'angular.js:src/ng/location.js':[ +222, +], +'angular.js:src/ng/testability.js':[ +42, +], +} diff --git a/its/ruling/src/test/expected/js/fireact/javascript-S6660.json b/its/ruling/src/test/expected/js/fireact/javascript-S6660.json new file mode 100644 index 0000000000..6eddc82a75 --- /dev/null +++ b/its/ruling/src/test/expected/js/fireact/javascript-S6660.json @@ -0,0 +1,5 @@ +{ +'fireact:functions/index.js':[ +471, +], +} diff --git a/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S6660.json b/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S6660.json new file mode 100644 index 0000000000..c8a5acf4b2 --- /dev/null +++ b/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S6660.json @@ -0,0 +1,123 @@ +{ +'javascript-test-sources:src/ace/doc/template/resources/javascripts/jquery-scrollspy.js':[ +79, +], +'javascript-test-sources:src/ace/experiments/dom.html':[ +284, +], +'javascript-test-sources:src/ace/lib/ace/mode/css/csslint.js':[ +2038, +7562, +8362, +10094, +], +'javascript-test-sources:src/ace/lib/ace/mode/html/saxparser.js':[ +526, +2629, +9827, +], +'javascript-test-sources:src/ace/lib/ace/mode/javascript/jshint.js':[ +5310, +7210, +7218, +9505, +9798, +9849, +15493, +18635, +], +'javascript-test-sources:src/ace/lib/ace/mode/php/php.js':[ +1255, +], +'javascript-test-sources:src/ace/lib/ace/worker/worker_v2.js':[ +80, +], +'javascript-test-sources:src/ace/src/ext/rtl.js':[ +77, +], +'javascript-test-sources:src/ace/src/keyboard/vim.js':[ +2301, +2419, +2427, +3717, +4906, +5313, +5353, +5685, +5915, +6269, +], +'javascript-test-sources:src/ace/src/keyboard/vim_test.js':[ +4794, +], +'javascript-test-sources:src/ace/src/keyboard/vscode.js':[ +32, +45, +58, +], +'javascript-test-sources:src/ace/src/layer/gutter.js':[ +332, +], +'javascript-test-sources:src/ace/src/lib/bidiutil.js':[ +110, +], +'javascript-test-sources:src/ace/src/mode/folding/ruby.js':[ +172, +], +'javascript-test-sources:src/ace/src/mode/folding/vbscript.js':[ +224, +], +'javascript-test-sources:src/ace/src/mouse/multi_select_handler.js':[ +58, +], +'javascript-test-sources:src/ace/src/multi_select.js':[ +602, +], +'javascript-test-sources:src/ace/src/test/asyncjs/async.js':[ +350, +409, +], +'javascript-test-sources:src/ace/src/virtual_renderer.js':[ +647, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/api/users.js':[ +313, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/config/url.js':[ +24, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/data/meta/cover_image.js':[ +13, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/middleware/auth.js':[ +180, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/models/post.js':[ +75, +137, +], +'javascript-test-sources:src/ecmascript6/Ghost/core/server/models/user.js':[ +63, +], +'javascript-test-sources:src/ecmascript6/router/third_party/brick/brick-1.0.1.byob.js':[ +2106, +], +'javascript-test-sources:src/ecmascript6/sonar-web/src/main/js/components/common/select-list.js':[ +258, +], +'javascript-test-sources:src/ecmascript6/sonar-web/src/main/js/components/navigator/controller.js':[ +116, +], +'javascript-test-sources:src/ecmascript6/sonar-web/src/main/js/components/navigator/filters/metric-filters.js':[ +38, +], +'javascript-test-sources:src/ecmascript6/watchtower.js/src/dirty_checking.js':[ +954, +], +'javascript-test-sources:src/ecmascript6/watchtower.js/src/watch_record.js':[ +245, +], +'javascript-test-sources:src/ecmascript6/watchtower.js/test/matchers.js':[ +13, +], +} diff --git a/its/ruling/src/test/expected/js/jquery/javascript-S6660.json b/its/ruling/src/test/expected/js/jquery/javascript-S6660.json new file mode 100644 index 0000000000..7362b21313 --- /dev/null +++ b/its/ruling/src/test/expected/js/jquery/javascript-S6660.json @@ -0,0 +1,11 @@ +{ +'jquery:src/css/showHide.js':[ +59, +], +'jquery:src/effects.js':[ +461, +], +'jquery:src/selector.js':[ +191, +], +} diff --git a/its/ruling/src/test/expected/js/jshint/javascript-S6660.json b/its/ruling/src/test/expected/js/jshint/javascript-S6660.json new file mode 100644 index 0000000000..34a3c6d992 --- /dev/null +++ b/its/ruling/src/test/expected/js/jshint/javascript-S6660.json @@ -0,0 +1,13 @@ +{ +'jshint:src/jshint.js':[ +867, +2708, +2714, +4957, +5252, +5306, +], +'jshint:src/scope-manager.js':[ +382, +], +} diff --git a/its/ruling/src/test/expected/js/knockout/javascript-S6660.json b/its/ruling/src/test/expected/js/knockout/javascript-S6660.json new file mode 100644 index 0000000000..58c47b9400 --- /dev/null +++ b/its/ruling/src/test/expected/js/knockout/javascript-S6660.json @@ -0,0 +1,5 @@ +{ +'knockout:src/utils.js':[ +185, +], +} diff --git a/its/ruling/src/test/expected/js/mootools-core/javascript-S6660.json b/its/ruling/src/test/expected/js/mootools-core/javascript-S6660.json new file mode 100644 index 0000000000..45d49cd327 --- /dev/null +++ b/its/ruling/src/test/expected/js/mootools-core/javascript-S6660.json @@ -0,0 +1,10 @@ +{ +'mootools-core:Grunt/plugins/eslint/rules/mootools-indent.js':[ +369, +], +'mootools-core:Source/Slick/Slick.Finder.js':[ +449, +539, +601, +], +} diff --git a/its/ruling/src/test/expected/js/ocanvas/javascript-S6660.json b/its/ruling/src/test/expected/js/ocanvas/javascript-S6660.json new file mode 100644 index 0000000000..2a03b44453 --- /dev/null +++ b/its/ruling/src/test/expected/js/ocanvas/javascript-S6660.json @@ -0,0 +1,11 @@ +{ +'ocanvas:src/events.js':[ +185, +], +'ocanvas:src/style.js':[ +274, +], +'ocanvas:src/tools.js':[ +309, +], +} diff --git a/its/ruling/src/test/expected/js/p5.js/javascript-S6660.json b/its/ruling/src/test/expected/js/p5.js/javascript-S6660.json new file mode 100644 index 0000000000..02b5c3b0da --- /dev/null +++ b/its/ruling/src/test/expected/js/p5.js/javascript-S6660.json @@ -0,0 +1,35 @@ +{ +'p5.js:lib/addons/p5.sound.js':[ +2639, +5589, +10793, +], +'p5.js:src/core/environment.js':[ +549, +], +'p5.js:src/core/p5.Renderer2D.js':[ +820, +], +'p5.js:src/core/rendering.js':[ +69, +104, +], +'p5.js:src/core/shape/vertex.js':[ +374, +], +'p5.js:src/dom/dom.js':[ +678, +706, +1951, +2810, +], +'p5.js:src/events/mouse.js':[ +616, +], +'p5.js:src/io/p5.TableRow.js':[ +92, +], +'p5.js:src/typography/p5.Font.js':[ +1123, +], +} diff --git a/its/ruling/src/test/expected/js/paper.js/javascript-S6660.json b/its/ruling/src/test/expected/js/paper.js/javascript-S6660.json new file mode 100644 index 0000000000..3a2c733007 --- /dev/null +++ b/its/ruling/src/test/expected/js/paper.js/javascript-S6660.json @@ -0,0 +1,11 @@ +{ +'paper.js:src/core/PaperScript.js':[ +280, +], +'paper.js:src/node/extend.js':[ +135, +], +'paper.js:src/path/Curve.js':[ +1851, +], +} diff --git a/its/ruling/src/test/expected/js/prototype/javascript-S6660.json b/its/ruling/src/test/expected/js/prototype/javascript-S6660.json new file mode 100644 index 0000000000..1f7213f1e5 --- /dev/null +++ b/its/ruling/src/test/expected/js/prototype/javascript-S6660.json @@ -0,0 +1,8 @@ +{ +'prototype:src/prototype/dom/dom.js':[ +2996, +], +'prototype:src/prototype/dom/layout.js':[ +97, +], +} diff --git a/its/ruling/src/test/expected/js/qunit/javascript-S6660.json b/its/ruling/src/test/expected/js/qunit/javascript-S6660.json new file mode 100644 index 0000000000..fec8ff7698 --- /dev/null +++ b/its/ruling/src/test/expected/js/qunit/javascript-S6660.json @@ -0,0 +1,11 @@ +{ +'qunit:reporter/diff.js':[ +845, +], +'qunit:src/assert.js':[ +421, +], +'qunit:src/reports/suite.js':[ +104, +], +} diff --git a/its/ruling/src/test/expected/js/sizzle/javascript-S6660.json b/its/ruling/src/test/expected/js/sizzle/javascript-S6660.json new file mode 100644 index 0000000000..79c240e587 --- /dev/null +++ b/its/ruling/src/test/expected/js/sizzle/javascript-S6660.json @@ -0,0 +1,5 @@ +{ +'sizzle:src/sizzle.js':[ +287, +], +} diff --git a/its/ruling/src/test/expected/ts/Joust/typescript-S6660.json b/its/ruling/src/test/expected/ts/Joust/typescript-S6660.json new file mode 100644 index 0000000000..3fed476158 --- /dev/null +++ b/its/ruling/src/test/expected/ts/Joust/typescript-S6660.json @@ -0,0 +1,5 @@ +{ +'Joust:ts/components/game/Player.tsx':[ +285, +], +} diff --git a/its/ruling/src/test/expected/ts/TypeScript/typescript-S6660.json b/its/ruling/src/test/expected/ts/TypeScript/typescript-S6660.json new file mode 100644 index 0000000000..8180adad6d --- /dev/null +++ b/its/ruling/src/test/expected/ts/TypeScript/typescript-S6660.json @@ -0,0 +1,92 @@ +{ +'TypeScript:src/compiler/binder.ts':[ +392, +], +'TypeScript:src/compiler/checker.ts':[ +4421, +9532, +12349, +16817, +17635, +18392, +21342, +21548, +23885, +], +'TypeScript:src/compiler/commandLineParser.ts':[ +954, +1369, +], +'TypeScript:src/compiler/core.ts':[ +1467, +], +'TypeScript:src/compiler/moduleNameResolver.ts':[ +171, +219, +242, +431, +839, +], +'TypeScript:src/compiler/parser.ts':[ +5311, +7492, +], +'TypeScript:src/compiler/program.ts':[ +1618, +], +'TypeScript:src/compiler/transformers/es2015.ts':[ +628, +], +'TypeScript:src/compiler/transformers/module/module.ts':[ +654, +], +'TypeScript:src/harness/fourslash.ts':[ +713, +1244, +1324, +2355, +2971, +2999, +], +'TypeScript:src/harness/harness.ts':[ +931, +], +'TypeScript:src/harness/sourceMapRecorder.ts':[ +251, +], +'TypeScript:src/server/editorServices.ts':[ +513, +1219, +1304, +], +'TypeScript:src/server/scriptVersionCache.ts':[ +206, +221, +], +'TypeScript:src/server/session.ts':[ +911, +], +'TypeScript:src/server/typingsInstaller/typingsInstaller.ts':[ +170, +244, +], +'TypeScript:src/services/completions.ts':[ +547, +], +'TypeScript:src/services/documentRegistry.ts':[ +192, +], +'TypeScript:src/services/findAllReferences.ts':[ +828, +], +'TypeScript:src/services/formatting/formatting.ts':[ +773, +], +'TypeScript:src/services/patternMatcher.ts':[ +236, +581, +], +'TypeScript:src/services/transpile.ts':[ +147, +], +} diff --git a/its/ruling/src/test/expected/ts/ag-grid/typescript-S6660.json b/its/ruling/src/test/expected/ts/ag-grid/typescript-S6660.json new file mode 100644 index 0000000000..58218f9fc0 --- /dev/null +++ b/its/ruling/src/test/expected/ts/ag-grid/typescript-S6660.json @@ -0,0 +1,59 @@ +{ +'ag-grid:src/ts/cellNavigationService.ts':[ +73, +124, +], +'ag-grid:src/ts/columnController/columnUtils.ts':[ +58, +], +'ag-grid:src/ts/columnController/displayedGroupCreator.ts':[ +182, +], +'ag-grid:src/ts/entities/rowNode.ts':[ +404, +], +'ag-grid:src/ts/gridCore.ts':[ +292, +], +'ag-grid:src/ts/gridPanel/gridPanel.ts':[ +967, +1009, +1127, +], +'ag-grid:src/ts/gridSerializer.ts':[ +281, +], +'ag-grid:src/ts/headerRendering/deprecated/renderedHeaderCell.ts':[ +383, +], +'ag-grid:src/ts/headerRendering/header/headerWrapperComp.ts':[ +254, +], +'ag-grid:src/ts/headerRendering/headerGroup/headerGroupWrapperComp.ts':[ +290, +], +'ag-grid:src/ts/rendering/cellRenderers/groupCellRenderer.ts':[ +164, +], +'ag-grid:src/ts/rendering/renderedCell.ts':[ +499, +722, +], +'ag-grid:src/ts/rendering/renderedRow.ts':[ +1141, +1183, +], +'ag-grid:src/ts/rowNodes/filterService.ts':[ +37, +], +'ag-grid:src/ts/selectionController.ts':[ +208, +], +'ag-grid:src/ts/utils.ts':[ +462, +716, +], +'ag-grid:src/ts/widgets/component.ts':[ +37, +], +} diff --git a/its/ruling/src/test/expected/ts/console/typescript-S6660.json b/its/ruling/src/test/expected/ts/console/typescript-S6660.json new file mode 100644 index 0000000000..3d6a6af2c9 --- /dev/null +++ b/its/ruling/src/test/expected/ts/console/typescript-S6660.json @@ -0,0 +1,14 @@ +{ +'console:src/views/Settings/Billing/CreditCardInputSection.tsx':[ +499, +], +'console:src/views/Settings/Billing/EditCreditCard.tsx':[ +233, +], +'console:src/views/models/DatabrowserView/RelationsPopup.tsx':[ +270, +], +'console:src/views/models/FieldPopup/FieldPopup.tsx':[ +417, +], +} diff --git a/its/ruling/src/test/expected/ts/courselit/typescript-S6660.json b/its/ruling/src/test/expected/ts/courselit/typescript-S6660.json new file mode 100644 index 0000000000..f558a9e11e --- /dev/null +++ b/its/ruling/src/test/expected/ts/courselit/typescript-S6660.json @@ -0,0 +1,8 @@ +{ +'courselit:apps/web/graphql/courses/logic.ts':[ +40, +], +'courselit:apps/web/graphql/lessons/logic.ts':[ +36, +], +} diff --git a/its/ruling/src/test/expected/ts/desktop/typescript-S6660.json b/its/ruling/src/test/expected/ts/desktop/typescript-S6660.json new file mode 100644 index 0000000000..aea9f4625f --- /dev/null +++ b/its/ruling/src/test/expected/ts/desktop/typescript-S6660.json @@ -0,0 +1,33 @@ +{ +'desktop:app/src/lib/patch-formatter.ts':[ +294, +], +'desktop:app/src/lib/stores/sign-in-store.ts':[ +389, +], +'desktop:app/src/main-process/main.ts':[ +776, +], +'desktop:app/src/ui/changes/commit-message.tsx':[ +668, +], +'desktop:app/src/ui/diff/syntax-highlighting/index.ts':[ +186, +], +'desktop:app/src/ui/diff/text-diff-expansion.ts':[ +236, +], +'desktop:app/src/ui/history/commit-summary.tsx':[ +248, +285, +], +'desktop:app/src/ui/lib/list/list.tsx':[ +1239, +], +'desktop:app/src/ui/lib/parse-files-to-be-overwritten.ts':[ +15, +], +'desktop:app/src/ui/relative-time.tsx':[ +119, +], +} diff --git a/its/ruling/src/test/expected/ts/eigen/typescript-S6660.json b/its/ruling/src/test/expected/ts/eigen/typescript-S6660.json new file mode 100644 index 0000000000..de7d2190ad --- /dev/null +++ b/its/ruling/src/test/expected/ts/eigen/typescript-S6660.json @@ -0,0 +1,30 @@ +{ +'eigen:src/app/Components/Bidding/Screens/ConfirmBid/index.tsx':[ +216, +], +'eigen:src/app/Components/Bidding/Screens/Registration.tsx':[ +250, +], +'eigen:src/app/Components/FancyModal/FancyModal.tsx':[ +98, +], +'eigen:src/app/NativeModules/ARScreenPresenterModule.tsx':[ +112, +], +'eigen:src/app/Scenes/Inbox/Components/Conversations/ConversationCTA.tsx':[ +65, +], +'eigen:src/app/Scenes/MyCollection/Screens/ArtworkForm/MyCollectionArtworkForm.tsx':[ +374, +], +'eigen:src/app/relay/middlewares/cacheMiddleware.ts':[ +46, +], +'eigen:src/app/utils/saleTime.ts':[ +56, +79, +], +'eigen:src/setupJest.ts':[ +529, +], +} diff --git a/its/ruling/src/test/expected/ts/rxjs/typescript-S6660.json b/its/ruling/src/test/expected/ts/rxjs/typescript-S6660.json new file mode 100644 index 0000000000..f660942834 --- /dev/null +++ b/its/ruling/src/test/expected/ts/rxjs/typescript-S6660.json @@ -0,0 +1,5 @@ +{ +'rxjs:src/observable/PromiseObservable.ts':[ +82, +], +} diff --git a/its/ruling/src/test/expected/ts/vuetify/typescript-S6660.json b/its/ruling/src/test/expected/ts/vuetify/typescript-S6660.json new file mode 100644 index 0000000000..b31c58c8e8 --- /dev/null +++ b/its/ruling/src/test/expected/ts/vuetify/typescript-S6660.json @@ -0,0 +1,11 @@ +{ +'vuetify:packages/vuetify/src/components/VSlideGroup/VSlideGroup.tsx':[ +235, +], +'vuetify:packages/vuetify/src/composables/theme.ts':[ +316, +], +'vuetify:packages/vuetify/src/directives/ripple/index.ts':[ +181, +], +} diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java index 5bd19f3639..766aa79c44 100644 --- a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/CheckList.java @@ -258,6 +258,7 @@ public static List> getAllChecks() { NoInferrableTypesCheck.class, NoInvalidAwaitCheck.class, NoInvertedBooleanCheckCheck.class, + NoLonelyIfCheck.class, NoLossOfPrecisionCheck.class, NoMagicNumbersCheck.class, NoMimeSniffCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoLonelyIfCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoLonelyIfCheck.java new file mode 100644 index 0000000000..1a37fab6a3 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoLonelyIfCheck.java @@ -0,0 +1,36 @@ +/** + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.javascript.checks; + +import org.sonar.check.Rule; +import org.sonar.plugins.javascript.api.EslintBasedCheck; +import org.sonar.plugins.javascript.api.JavaScriptRule; +import org.sonar.plugins.javascript.api.TypeScriptRule; + +@TypeScriptRule +@JavaScriptRule +@Rule(key = "S6660") +public class NoLonelyIfCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "no-lonely-if"; + } +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6660.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6660.html new file mode 100644 index 0000000000..02feb74859 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6660.html @@ -0,0 +1,48 @@ +

Why is this an issue?

+

When if is the only statement in the else block, it is better to use else if because it simplifies the code +and makes it more readable.

+

When using nested if statements, it can be difficult to keep track of the logic and understand the flow of the code. Using else +if makes the code more concise and easier to follow.

+
+if (condition1) {
+    // ...
+} else {
+    if (condition2) {  // Noncompliant
+        // ...
+    }
+}
+
+
+if (condition3) {
+    // ...
+} else {
+    if (condition4) { // Noncompliant
+        // ...
+    } else {
+        // ...
+    }
+}
+
+

Fix your code by using else if instead of nested if block.

+
+if (condition1) {
+    // ...
+} else if (condition2) {
+    // ...
+}
+
+
+if (condition3) {
+    // ...
+} else if (condition4) {
+    // ...
+} else {
+    // ...
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6660.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6660.json new file mode 100644 index 0000000000..b06d737127 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6660.json @@ -0,0 +1,19 @@ +{ + "title": "If statements should not be the only statement in else blocks", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6660", + "sqKey": "S6660", + "scope": "All", + "quickfix": "covered", + "compatibleLanguages": [ + "JAVASCRIPT", + "TYPESCRIPT" + ] +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json index d38adad0bb..e1df509d18 100644 --- a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/Sonar_way_profile.json @@ -264,6 +264,7 @@ "S6647", "S6650", "S6654", - "S6657" + "S6657", + "S6660" ] } diff --git a/src/linting/eslint/linter/quickfixes/messages.ts b/src/linting/eslint/linter/quickfixes/messages.ts index c8f4316cea..b540aa67fb 100644 --- a/src/linting/eslint/linter/quickfixes/messages.ts +++ b/src/linting/eslint/linter/quickfixes/messages.ts @@ -44,6 +44,7 @@ const quickFixMessages = new Map([ ['prefer-while', "Replace with 'while' loop"], ['no-empty-interface', 'Replace with type alias'], ['no-inferrable-types', 'Remove type declaration'], + ['no-lonely-if', "Replace with 'else if'"], ['no-undef-init', 'Remove initialization'], ['no-unnecessary-type-arguments', 'Remove type argument'], ['no-unnecessary-type-assertion', 'Remove type assertion'], diff --git a/src/linting/eslint/linter/quickfixes/rules.ts b/src/linting/eslint/linter/quickfixes/rules.ts index 5774bd94f0..8396daac7a 100644 --- a/src/linting/eslint/linter/quickfixes/rules.ts +++ b/src/linting/eslint/linter/quickfixes/rules.ts @@ -56,6 +56,7 @@ export const quickFixRules = new Set([ 'no-duplicate-imports', 'no-empty-function', 'no-empty', + 'no-lonely-if', 'no-throw-literal', 'no-unreachable', 'no-useless-constructor', diff --git a/src/linting/eslint/rules/decorators/index.ts b/src/linting/eslint/rules/decorators/index.ts index 76fec2cbd6..beceadbd20 100644 --- a/src/linting/eslint/rules/decorators/index.ts +++ b/src/linting/eslint/rules/decorators/index.ts @@ -33,6 +33,7 @@ import { decorateNoEmptyFunction } from './no-empty-function-decorator'; import { decorateNoEmptyInterface } from './no-empty-interface-decorator'; import { decorateNoExtendNative } from './no-extend-native-decorator'; import { decorateNoExtraSemi } from './no-extra-semi-decorator'; +import { decorateNoLonelyIf } from './no-lonely-if-decorator'; import { decorateNoRedeclare } from './no-redeclare-decorator'; import { decorateNoThisAlias } from './no-this-alias-decorator'; import { decorateNoThrowLiteral } from './no-throw-literal-decorator'; @@ -83,6 +84,7 @@ export const decorators: Record = { 'no-empty-interface': decorateNoEmptyInterface, 'no-extend-native': decorateNoExtendNative, 'no-extra-semi': decorateNoExtraSemi, + 'no-lonely-if': decorateNoLonelyIf, 'no-redeclare': decorateNoRedeclare, 'no-redundant-type-constituents': decorateNoRedundantTypeConstituents, 'no-this-alias': decorateNoThisAlias, diff --git a/src/linting/eslint/rules/decorators/no-lonely-if-decorator.ts b/src/linting/eslint/rules/decorators/no-lonely-if-decorator.ts new file mode 100644 index 0000000000..32e5cd894a --- /dev/null +++ b/src/linting/eslint/rules/decorators/no-lonely-if-decorator.ts @@ -0,0 +1,44 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// https://sonarsource.github.io/rspec/#/rspec/S6660/javascript + +import { Rule } from 'eslint'; +import { interceptReport } from './helpers'; + +export function decorateNoLonelyIf(rule: Rule.RuleModule): Rule.RuleModule { + return interceptReport(rule, (context, reportDescriptor) => { + if ('node' in reportDescriptor && 'messageId' in reportDescriptor) { + const { node, messageId, ...rest } = reportDescriptor; + + if (node.type === 'IfStatement' && node.loc && messageId === 'unexpectedLonelyIf') { + const { start } = node.loc; + + context.report({ + message: "'If' statement should not be the only statement in 'else' block", + loc: { + start, + end: { line: start.line, column: start.column + 2 }, + }, + ...rest, + }); + } + } + }); +} diff --git a/tests/linting/eslint/linter/fixtures/wrapper/quickfixes/no-lonely-if.js b/tests/linting/eslint/linter/fixtures/wrapper/quickfixes/no-lonely-if.js new file mode 100644 index 0000000000..e342e8fb15 --- /dev/null +++ b/tests/linting/eslint/linter/fixtures/wrapper/quickfixes/no-lonely-if.js @@ -0,0 +1,8 @@ +if (condition) { + // ... +} else { + if (anotherCondition) { + // ... + } +} + diff --git a/tests/linting/eslint/rules/decorators/no-lonely-if.test.ts b/tests/linting/eslint/rules/decorators/no-lonely-if.test.ts new file mode 100644 index 0000000000..54d2b68915 --- /dev/null +++ b/tests/linting/eslint/rules/decorators/no-lonely-if.test.ts @@ -0,0 +1,97 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { RuleTester } from 'eslint'; +import { eslintRules } from 'linting/eslint/rules/core'; +import { decorateNoLonelyIf } from 'linting/eslint/rules/decorators/no-lonely-if-decorator'; + +const rule = decorateNoLonelyIf(eslintRules['no-lonely-if']); +const ruleTester = new RuleTester(); + +ruleTester.run("'If' statement should not be the only statement in 'else' block", rule, { + valid: [ + { + code: ` + if (condition) { + doSomething(); + } + `, + }, + ], + invalid: [ + { + code: ` + if (condition1) { + // ... + } else { + if (condition2) { + // ... + } + } + `, + output: ` + if (condition1) { + // ... + } else if (condition2) { + // ... + } + `, + errors: [ + { + message: "'If' statement should not be the only statement in 'else' block", + line: 5, + endLine: 5, + column: 11, + endColumn: 13, + }, + ], + }, + { + code: ` + if (condition3) { + // ... + } else { + if (condition4) { + // ... + } else { + // ... + } + } + `, + output: ` + if (condition3) { + // ... + } else if (condition4) { + // ... + } else { + // ... + } + `, + errors: [ + { + message: "'If' statement should not be the only statement in 'else' block", + line: 5, + endLine: 5, + column: 11, + endColumn: 13, + }, + ], + }, + ], +});