diff --git a/its/ruling/src/test/expected/jsts/Joust/typescript-S6836.json b/its/ruling/src/test/expected/jsts/Joust/typescript-S6836.json new file mode 100644 index 00000000000..83684a2e570 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/Joust/typescript-S6836.json @@ -0,0 +1,24 @@ +{ +"Joust:ts/components/EventLogLine.tsx": [ +211 +], +"Joust:ts/components/GameWrapper.tsx": [ +135, +136 +], +"Joust:ts/components/game/Player.tsx": [ +343 +], +"Joust:ts/protocol/HSReplayDecoder.ts": [ +96, +131, +143, +320, +321, +357 +], +"Joust:ts/protocol/KettleDecoder.ts": [ +130, +145 +] +} diff --git a/its/ruling/src/test/expected/jsts/TypeScript/typescript-S6836.json b/its/ruling/src/test/expected/jsts/TypeScript/typescript-S6836.json new file mode 100644 index 00000000000..d59d1c2a4d7 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/TypeScript/typescript-S6836.json @@ -0,0 +1,199 @@ +{ +"TypeScript:scripts/buildProtocol.ts": [ +86 +], +"TypeScript:scripts/tslint/noIncrementDecrementRule.ts": [ +18 +], +"TypeScript:src/compiler/binder.ts": [ +288, +289, +292, +293, +2026, +2088, +2089, +2356, +2357, +2749, +2750 +], +"TypeScript:src/compiler/checker.ts": [ +921, +3691, +6652, +7729, +7990, +8011, +11410, +11411, +11412, +15100, +16593, +16599, +16996, +17025, +17073, +17074, +17674, +17883, +17884, +18567, +18568, +18874, +18875, +18897, +18898, +18949, +19017, +21054, +21065, +21069, +21095, +21096, +21097, +21098, +21146, +21151, +21380, +21398, +22036, +22054, +22323, +22333, +22341, +22752, +23311 +], +"TypeScript:src/compiler/commandLineParser.ts": [ +792, +804 +], +"TypeScript:src/compiler/factory.ts": [ +2800 +], +"TypeScript:src/compiler/parser.ts": [ +1696, +1697, +2526, +6404, +6425, +6862 +], +"TypeScript:src/compiler/program.ts": [ +1142, +1161, +1388 +], +"TypeScript:src/compiler/scanner.ts": [ +650, +651, +1582 +], +"TypeScript:src/compiler/transformers/es2015.ts": [ +1540, +2657, +3026 +], +"TypeScript:src/compiler/transformers/es5.ts": [ +53 +], +"TypeScript:src/compiler/transformers/ts.ts": [ +1804, +1805, +1861 +], +"TypeScript:src/compiler/utilities.ts": [ +547, +680, +782, +1271, +1293, +1299, +1686, +1692 +], +"TypeScript:src/harness/harness.ts": [ +90, +304 +], +"TypeScript:src/server/scriptInfo.ts": [ +273 +], +"TypeScript:src/server/server.ts": [ +454 +], +"TypeScript:src/server/session.ts": [ +423, +428, +432 +], +"TypeScript:src/server/utilities.ts": [ +44 +], +"TypeScript:src/services/breakpoints.ts": [ +565, +569, +606, +607, +615, +633 +], +"TypeScript:src/services/codefixes/helpers.ts": [ +77, +78, +95, +106 +], +"TypeScript:src/services/codefixes/unusedIdentifierFixes.ts": [ +49, +66, +76, +80, +94, +113, +136, +137, +146, +148, +157 +], +"TypeScript:src/services/completions.ts": [ +408, +992 +], +"TypeScript:src/services/documentHighlights.ts": [ +82 +], +"TypeScript:src/services/formatting/formatting.ts": [ +177, +1100, +1111 +], +"TypeScript:src/services/importTracker.ts": [ +105 +], +"TypeScript:src/services/navigationBar.ts": [ +159, +187, +197, +212, +213, +412 +], +"TypeScript:src/services/outliningElementsCollector.ts": [ +168, +169 +], +"TypeScript:src/services/services.ts": [ +620, +621, +693, +1378 +], +"TypeScript:src/services/utilities.ts": [ +517, +531, +537 +] +} diff --git a/its/ruling/src/test/expected/jsts/ag-grid/typescript-S6836.json b/its/ruling/src/test/expected/jsts/ag-grid/typescript-S6836.json new file mode 100644 index 00000000000..13ba3d664d0 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ag-grid/typescript-S6836.json @@ -0,0 +1,9 @@ +{ +"ag-grid:src/ts/gridPanel/gridPanel.ts": [ +453, +458 +], +"ag-grid:src/ts/headerRendering/headerRowComp.ts": [ +223 +] +} diff --git a/its/ruling/src/test/expected/jsts/console/typescript-S6836.json b/its/ruling/src/test/expected/jsts/console/typescript-S6836.json new file mode 100644 index 00000000000..805acc31c24 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/console/typescript-S6836.json @@ -0,0 +1,18 @@ +{ +"console:src/reducers/databrowser/data.ts": [ +31, +44, +105, +113, +114 +], +"console:src/reducers/databrowser/ui.ts": [ +73 +], +"console:src/reducers/gettingStarted.ts": [ +18 +], +"console:src/views/models/DatabrowserView/DatabrowserView.tsx": [ +624 +] +} diff --git a/its/ruling/src/test/expected/jsts/desktop/typescript-S6836.json b/its/ruling/src/test/expected/jsts/desktop/typescript-S6836.json new file mode 100644 index 00000000000..9e7678a8d3a --- /dev/null +++ b/its/ruling/src/test/expected/jsts/desktop/typescript-S6836.json @@ -0,0 +1,76 @@ +{ +"desktop:app/src/lib/shells/win32.ts": [ +419, +426, +433, +440, +465, +469 +], +"desktop:app/src/lib/status.ts": [ +130 +], +"desktop:app/src/lib/stores/app-store.ts": [ +6730 +], +"desktop:app/src/lib/trampoline/trampoline-command-parser.ts": [ +73, +83, +84 +], +"desktop:app/src/models/app-menu.ts": [ +220 +], +"desktop:app/src/ui/about/about.tsx": [ +117 +], +"desktop:app/src/ui/app.tsx": [ +1373, +1412, +1416, +1449, +1595, +1680, +1681, +1927, +1929, +1974, +1978, +1979, +1982, +1986, +1995 +], +"desktop:app/src/ui/clone-repository/clone-repository.tsx": [ +300 +], +"desktop:app/src/ui/diff/side-by-side-diff-row.tsx": [ +171 +], +"desktop:app/src/ui/dispatcher/dispatcher.ts": [ +1818, +1827, +1828, +1839, +3704 +], +"desktop:app/src/ui/drag-elements/commit-drag-element.tsx": [ +74, +91, +108 +], +"desktop:app/src/ui/multi-commit-operation/base-multi-commit-operation.tsx": [ +157, +216 +], +"desktop:app/src/ui/sign-in/sign-in.tsx": [ +138 +], +"desktop:app/src/ui/welcome/welcome.tsx": [ +141 +], +"desktop:script/draft-release/version.ts": [ +48, +64 +] +} diff --git a/its/ruling/src/test/expected/jsts/eigen/javascript-S6836.json b/its/ruling/src/test/expected/jsts/eigen/javascript-S6836.json new file mode 100644 index 00000000000..6eb7665ee83 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/eigen/javascript-S6836.json @@ -0,0 +1,8 @@ +{ +"eigen:scripts/strictness-migration.js": [ +44, +47, +79, +81 +] +} diff --git a/its/ruling/src/test/expected/jsts/eigen/typescript-S6836.json b/its/ruling/src/test/expected/jsts/eigen/typescript-S6836.json new file mode 100644 index 00000000000..83212b96ce7 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/eigen/typescript-S6836.json @@ -0,0 +1,19 @@ +{ +"eigen:src/app/Scenes/Feature/Feature.tsx": [ +117, +118, +120, +121 +], +"eigen:src/app/Scenes/Onboarding/OnboardingCreateAccount/OnboardingCreateAccount.tsx": [ +103 +], +"eigen:src/app/store/NativeModel.ts": [ +94 +], +"eigen:src/app/utils/ArtworkInquiry/ArtworkInquiryStore.tsx": [ +42, +43, +44 +] +} diff --git a/its/ruling/src/test/expected/jsts/postgraphql/typescript-S6836.json b/its/ruling/src/test/expected/jsts/postgraphql/typescript-S6836.json new file mode 100644 index 00000000000..ddfcbd02d6a --- /dev/null +++ b/its/ruling/src/test/expected/jsts/postgraphql/typescript-S6836.json @@ -0,0 +1,6 @@ +{ +"postgraphql:src/postgres/inventory/conditionToSql.ts": [ +30, +32 +] +} diff --git a/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S6836.json b/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S6836.json new file mode 100644 index 00000000000..64c65fa393d --- /dev/null +++ b/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S6836.json @@ -0,0 +1,5 @@ +{ +"reddit-mobile:index.js": [ +201 +] +} diff --git a/its/ruling/src/test/expected/jsts/redux/javascript-S6836.json b/its/ruling/src/test/expected/jsts/redux/javascript-S6836.json new file mode 100644 index 00000000000..f013e43ca7b --- /dev/null +++ b/its/ruling/src/test/expected/jsts/redux/javascript-S6836.json @@ -0,0 +1,14 @@ +{ +"redux:examples/real-world/reducers/paginate.js": [ +51 +], +"redux:examples/shopping-cart/reducers/cart.js": [ +27 +], +"redux:examples/shopping-cart/reducers/products.js": [ +27 +], +"redux:examples/todomvc/reducers/todos.js": [ +43 +] +} diff --git a/its/ruling/src/test/expected/jsts/vuetify/typescript-S6836.json b/its/ruling/src/test/expected/jsts/vuetify/typescript-S6836.json new file mode 100644 index 00000000000..df580a09772 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/vuetify/typescript-S6836.json @@ -0,0 +1,8 @@ +{ +"vuetify:packages/api-generator/src/types.ts": [ +270 +], +"vuetify:packages/vuetify/src/components/VCalendar/VCalendar.ts": [ +126 +] +} 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 c0d7530cead..db31f1e386f 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 @@ -240,6 +240,7 @@ public static List> getAllChecks() { NoArrayDeleteCheck.class, NoArrayIndexKeyCheck.class, NoBaseToStringCheck.class, + NoCaseDeclarationsCheck.class, NoChildrenPropCheck.class, NoClearTextProtocolsCheck.class, NoCodeAfterDoneCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoCaseDeclarationsCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoCaseDeclarationsCheck.java new file mode 100644 index 00000000000..64e8b8ce0ef --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoCaseDeclarationsCheck.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; + +@JavaScriptRule +@TypeScriptRule +@Rule(key = "S6836") +public class NoCaseDeclarationsCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "no-case-declarations"; + } +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6836.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6836.html new file mode 100644 index 00000000000..b14d7f0cbf4 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6836.html @@ -0,0 +1,49 @@ +

Why is this an issue?

+

The ECMAScript specification allows for creating block-level lexical declarations (let, const, function, and +class) in any block statement or expression. However, when these declarations are made inside a case or default +clause of a switch statement, they are not confined to the block of that case or default clause. Instead, they +apply to the whole switch block but only get initialized when the cases are reached, which can lead to unexpected behavior.

+
+switch (foo) {
+    case 1:
+        let x = 1; // Noncompliant
+        break;
+    case 2:
+        const y = 2; // Noncompliant
+        break;
+    case 3:
+        function f() {} // Noncompliant
+        break;
+    case 4:
+        class C {} // Noncompliant
+        break;
+}
+
+

To fix this, you can create a nested block within each case or default clause, ensuring each declaration is properly +scoped to its respective block.

+
+switch (foo) {
+    case 1: {
+        let x = 1;
+        break;
+    }
+    case 2: {
+        const y = 2;
+        break;
+    }
+    case 3: {
+        function f() {}
+        break;
+    }
+    case 4: {
+        class C {}
+        break;
+    }
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6836.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6836.json new file mode 100644 index 00000000000..2015e6b2598 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6836.json @@ -0,0 +1,25 @@ +{ + "title": "\"case\" and \"default\" clauses should not contain lexical declarations", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6836", + "sqKey": "S6836", + "scope": "All", + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "MEDIUM" + }, + "attribute": "CONVENTIONAL" + }, + "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 91b7eb4c9f6..38d39b0a02b 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 @@ -301,6 +301,7 @@ "S6823", "S6824", "S6825", - "S6827" + "S6827", + "S6836" ] }