From 11819f3081e6939b6e73a1b46612129bc88378b0 Mon Sep 17 00:00:00 2001 From: yassin-kammoun-sonarsource Date: Tue, 28 Nov 2023 15:47:30 +0100 Subject: [PATCH] Create rule S6861: Mutable variables should not be exported --- .../expected/jsts/Ghost/javascript-S6861.json | 14 ++++ .../jsts/TypeScript/typescript-S6861.json | 34 ++++++++ .../jsts/ag-grid/typescript-S6861.json | 5 ++ .../jsts/ant-design/typescript-S6861.json | 5 ++ .../jsts/console/javascript-S6861.json | 5 ++ .../jsts/console/typescript-S6861.json | 5 ++ .../expected/jsts/eigen/typescript-S6861.json | 5 ++ .../expected/jsts/http/javascript-S6861.json | 14 ++++ .../jsts/jquery/javascript-S6861.json | 20 +++++ .../jsts/reddit-mobile/javascript-S6861.json | 8 ++ .../expected/jsts/redux/javascript-S6861.json | 5 ++ .../jsts/router/javascript-S6861.json | 16 ++++ .../expected/jsts/rxjs/typescript-S6861.json | 81 +++++++++++++++++++ .../jsts/snoode/javascript-S6861.json | 5 ++ .../jsts/sonar-web/javascript-S6861.json | 5 ++ .../jsts/templating/javascript-S6861.json | 15 ++++ .../sonar/javascript/checks/CheckList.java | 1 + .../checks/NoMutableExportsCheck.java | 36 +++++++++ .../javascript/rules/javascript/S6861.html | 42 ++++++++++ .../javascript/rules/javascript/S6861.json | 26 ++++++ .../rules/javascript/Sonar_way_profile.json | 3 +- 21 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 its/ruling/src/test/expected/jsts/Ghost/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/TypeScript/typescript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/ag-grid/typescript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/ant-design/typescript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/console/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/console/typescript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/eigen/typescript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/http/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/jquery/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/redux/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/router/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/rxjs/typescript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/snoode/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/sonar-web/javascript-S6861.json create mode 100644 its/ruling/src/test/expected/jsts/templating/javascript-S6861.json create mode 100644 sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoMutableExportsCheck.java create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.html create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.json diff --git a/its/ruling/src/test/expected/jsts/Ghost/javascript-S6861.json b/its/ruling/src/test/expected/jsts/Ghost/javascript-S6861.json new file mode 100644 index 00000000000..492573e869e --- /dev/null +++ b/its/ruling/src/test/expected/jsts/Ghost/javascript-S6861.json @@ -0,0 +1,14 @@ +{ +"Ghost:core/client/app/app.js": [ +12 +], +"Ghost:core/client/app/router.js": [ +11 +], +"Ghost:core/client/app/utils/editor-shortcuts.js": [ +6 +], +"Ghost:core/client/tests/helpers/resolver.js": [ +4 +] +} diff --git a/its/ruling/src/test/expected/jsts/TypeScript/typescript-S6861.json b/its/ruling/src/test/expected/jsts/TypeScript/typescript-S6861.json new file mode 100644 index 00000000000..b58103053bb --- /dev/null +++ b/its/ruling/src/test/expected/jsts/TypeScript/typescript-S6861.json @@ -0,0 +1,34 @@ +{ +"TypeScript:src/compiler/commandLineParser.ts": [ +635 +], +"TypeScript:src/compiler/core.ts": [ +1217, +2224, +2242 +], +"TypeScript:src/compiler/sys.ts": [ +99 +], +"TypeScript:src/compiler/utilities.ts": [ +640, +641, +642, +4400 +], +"TypeScript:src/harness/harness.ts": [ +68, +499, +565, +589, +608, +688, +705, +780, +781, +874 +], +"TypeScript:src/services/services.ts": [ +938 +] +} diff --git a/its/ruling/src/test/expected/jsts/ag-grid/typescript-S6861.json b/its/ruling/src/test/expected/jsts/ag-grid/typescript-S6861.json new file mode 100644 index 00000000000..174943a9ede --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ag-grid/typescript-S6861.json @@ -0,0 +1,5 @@ +{ +"ag-grid:src/ts/utils.ts": [ +1080 +] +} diff --git a/its/ruling/src/test/expected/jsts/ant-design/typescript-S6861.json b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6861.json new file mode 100644 index 00000000000..2a482d1a8bd --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6861.json @@ -0,0 +1,5 @@ +{ +"ant-design:components/_util/warning.ts": [ +9 +] +} diff --git a/its/ruling/src/test/expected/jsts/console/javascript-S6861.json b/its/ruling/src/test/expected/jsts/console/javascript-S6861.json new file mode 100644 index 00000000000..d3162cba368 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/console/javascript-S6861.json @@ -0,0 +1,5 @@ +{ +"console:src/views/FunctionsView/FunctionLogs/Codemirror.js": [ +13 +] +} diff --git a/its/ruling/src/test/expected/jsts/console/typescript-S6861.json b/its/ruling/src/test/expected/jsts/console/typescript-S6861.json new file mode 100644 index 00000000000..42c403d0c9d --- /dev/null +++ b/its/ruling/src/test/expected/jsts/console/typescript-S6861.json @@ -0,0 +1,5 @@ +{ +"console:src/utils/metrics.ts": [ +3 +] +} diff --git a/its/ruling/src/test/expected/jsts/eigen/typescript-S6861.json b/its/ruling/src/test/expected/jsts/eigen/typescript-S6861.json new file mode 100644 index 00000000000..5c1ee996c9e --- /dev/null +++ b/its/ruling/src/test/expected/jsts/eigen/typescript-S6861.json @@ -0,0 +1,5 @@ +{ +"eigen:src/app/Scenes/MyProfile/MyProfilePayment.tsx": [ +38 +] +} diff --git a/its/ruling/src/test/expected/jsts/http/javascript-S6861.json b/its/ruling/src/test/expected/jsts/http/javascript-S6861.json new file mode 100644 index 00000000000..2a8bc1434b1 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/http/javascript-S6861.json @@ -0,0 +1,14 @@ +{ +"http:src/IConnection.js": [ +3 +], +"http:src/IInterceptResolution.js": [ +5 +], +"http:src/IRequest.js": [ +3 +], +"http:src/IResponse.js": [ +3 +] +} diff --git a/its/ruling/src/test/expected/jsts/jquery/javascript-S6861.json b/its/ruling/src/test/expected/jsts/jquery/javascript-S6861.json new file mode 100644 index 00000000000..3f90fad6615 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/jquery/javascript-S6861.json @@ -0,0 +1,20 @@ +{ +"jquery:src/core.js": [ +19 +], +"jquery:src/core/access.js": [ +6 +], +"jquery:src/core/init.js": [ +9 +], +"jquery:src/core/isAttached.js": [ +6 +], +"jquery:src/manipulation/wrapMap.js": [ +2 +], +"jquery:src/selector/rbuggyQSA.js": [ +5 +] +} diff --git a/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S6861.json b/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S6861.json new file mode 100644 index 00000000000..d329ae76bf3 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/reddit-mobile/javascript-S6861.json @@ -0,0 +1,8 @@ +{ +"reddit-mobile:src/server/oauth.es6.js": [ +18 +], +"reddit-mobile:src/server/routes.es6.js": [ +6 +] +} diff --git a/its/ruling/src/test/expected/jsts/redux/javascript-S6861.json b/its/ruling/src/test/expected/jsts/redux/javascript-S6861.json new file mode 100644 index 00000000000..a40c4b8387c --- /dev/null +++ b/its/ruling/src/test/expected/jsts/redux/javascript-S6861.json @@ -0,0 +1,5 @@ +{ +"redux:src/createStore.js": [ +9 +] +} diff --git a/its/ruling/src/test/expected/jsts/router/javascript-S6861.json b/its/ruling/src/test/expected/jsts/router/javascript-S6861.json new file mode 100644 index 00000000000..b4d71031842 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/router/javascript-S6861.json @@ -0,0 +1,16 @@ +{ +"router:src/activation.js": [ +5 +], +"router:src/navigationPlan.js": [ +1, +2, +3 +], +"router:src/pipeline.js": [ +10, +11, +12, +13 +] +} diff --git a/its/ruling/src/test/expected/jsts/rxjs/typescript-S6861.json b/its/ruling/src/test/expected/jsts/rxjs/typescript-S6861.json new file mode 100644 index 00000000000..1ca3b5c9504 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/rxjs/typescript-S6861.json @@ -0,0 +1,81 @@ +{ +"rxjs:src/Rx.ts": [ +193, +213 +], +"rxjs:src/add/observable/bindCallback.ts": [ +8 +], +"rxjs:src/add/observable/bindNodeCallback.ts": [ +8 +], +"rxjs:src/add/observable/combineLatest.ts": [ +8 +], +"rxjs:src/add/observable/concat.ts": [ +8 +], +"rxjs:src/add/observable/defer.ts": [ +8 +], +"rxjs:src/add/observable/dom/ajax.ts": [ +9 +], +"rxjs:src/add/observable/dom/webSocket.ts": [ +8 +], +"rxjs:src/add/observable/empty.ts": [ +8 +], +"rxjs:src/add/observable/forkJoin.ts": [ +8 +], +"rxjs:src/add/observable/from.ts": [ +8 +], +"rxjs:src/add/observable/fromEvent.ts": [ +8 +], +"rxjs:src/add/observable/fromEventPattern.ts": [ +8 +], +"rxjs:src/add/observable/fromPromise.ts": [ +8 +], +"rxjs:src/add/observable/generate.ts": [ +8 +], +"rxjs:src/add/observable/interval.ts": [ +8 +], +"rxjs:src/add/observable/merge.ts": [ +8 +], +"rxjs:src/add/observable/never.ts": [ +8 +], +"rxjs:src/add/observable/of.ts": [ +8 +], +"rxjs:src/add/observable/onErrorResumeNext.ts": [ +8 +], +"rxjs:src/add/observable/pairs.ts": [ +8 +], +"rxjs:src/add/observable/race.ts": [ +8 +], +"rxjs:src/add/observable/range.ts": [ +8 +], +"rxjs:src/add/observable/timer.ts": [ +8 +], +"rxjs:src/add/observable/using.ts": [ +8 +], +"rxjs:src/add/observable/zip.ts": [ +8 +] +} diff --git a/its/ruling/src/test/expected/jsts/snoode/javascript-S6861.json b/its/ruling/src/test/expected/jsts/snoode/javascript-S6861.json new file mode 100644 index 00000000000..321cff6aad6 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/snoode/javascript-S6861.json @@ -0,0 +1,5 @@ +{ +"snoode:src/api.es6.js": [ +16 +] +} diff --git a/its/ruling/src/test/expected/jsts/sonar-web/javascript-S6861.json b/its/ruling/src/test/expected/jsts/sonar-web/javascript-S6861.json new file mode 100644 index 00000000000..c22487cea9b --- /dev/null +++ b/its/ruling/src/test/expected/jsts/sonar-web/javascript-S6861.json @@ -0,0 +1,5 @@ +{ +"sonar-web:src/main/js/apps/update-center/plugins.js": [ +6 +] +} diff --git a/its/ruling/src/test/expected/jsts/templating/javascript-S6861.json b/its/ruling/src/test/expected/jsts/templating/javascript-S6861.json new file mode 100644 index 00000000000..4359f04cc30 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/templating/javascript-S6861.json @@ -0,0 +1,15 @@ +{ +"templating:src/lib/types.js": [ +3, +10, +17, +21, +25, +29, +35, +43, +57, +64, +73 +] +} 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 1607e850333..994591c743e 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 @@ -299,6 +299,7 @@ public static List> getAllChecks() { NoMisusedPromisesCheck.class, NoMixedContentCheck.class, NoMixedEnumsCheck.class, + NoMutableExportsCheck.class, NoNestedSwitchCheck.class, NoNestedTemplateLiteralsCheck.class, NoNewNativeNonconstructorCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoMutableExportsCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoMutableExportsCheck.java new file mode 100644 index 00000000000..a9477a360ed --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/NoMutableExportsCheck.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 = "S6861") +public class NoMutableExportsCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "no-mutable-exports"; + } +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.html new file mode 100644 index 00000000000..b6f2f080731 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.html @@ -0,0 +1,42 @@ +

Why is this an issue?

+

In JavaScript, a mutable variable is one whose value can be changed after it has been initially set. This is in contrast to immutable variables, +whose values cannot be changed once they are set.

+

Exporting mutable variables can lead to unpredictable behavior and bugs in your code. This is because any module that imports the variable can +change its value. If multiple modules import and change the value of the same variable, it can become difficult to track what the current value of the +variable is and which module changed it last.

+

How to fix it

+

If the value of the variable does not need to change, you can declare it as a constant using the const keyword. Alternatively, if you +have a group of related variables that need to be mutable, consider using a class to encapsulate them. You can then export an instance of the class, +or a factory function that creates instances of the class.

+

Code examples

+

Noncompliant code example

+
+let mutableVar = "initial value";
+
+export { mutableVar }; // Noncompliant
+
+

Compliant solution

+
+const immutableVar = "constant value";
+export { immutableVar };
+
+

or

+
+class MyClass {
+  constructor() {
+    this.mutableVar = "initial value";
+  }
+}
+
+export function createMyClass() {
+  return new MyClass();
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.json new file mode 100644 index 00000000000..e645c800a16 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6861.json @@ -0,0 +1,26 @@ +{ + "title": "Mutable variables should not be exported", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6861", + "sqKey": "S6861", + "scope": "All", + "quickfix": "infeasible", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH", + "RELIABILITY": "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 91b87e4e5a9..b86f1275c61 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 @@ -323,6 +323,7 @@ "S6852", "S6853", "S6854", - "S6855" + "S6855", + "S6861" ] }