From 5f2dbfe2f032b15e8a3f331c14a41eaa8820fe34 Mon Sep 17 00:00:00 2001 From: yassin-kammoun-sonarsource Date: Tue, 26 Mar 2024 11:32:52 +0100 Subject: [PATCH] Create rule S6959: "Array.reduce()" calls should include an initial value --- .../jsts/ant-design/typescript-S6959.json | 5 ++ .../jsts/desktop/typescript-S6959.json | 11 +++++ packages/jsts/src/rules/S6959/cb.fixture.ts | 13 +++++ packages/jsts/src/rules/S6959/cb.test.ts | 28 +++++++++++ packages/jsts/src/rules/S6959/index.ts | 20 ++++++++ packages/jsts/src/rules/S6959/rule.ts | 48 +++++++++++++++++++ packages/jsts/src/rules/S6959/unit.test.ts | 31 ++++++++++++ packages/jsts/src/rules/index.ts | 2 + .../sonar/javascript/checks/CheckList.java | 1 + .../checks/ReduceInitialValueCheck.java | 36 ++++++++++++++ .../javascript/rules/javascript/S6959.html | 32 +++++++++++++ .../javascript/rules/javascript/S6959.json | 27 +++++++++++ .../rules/javascript/Sonar_way_profile.json | 3 +- 13 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 its/ruling/src/test/expected/jsts/ant-design/typescript-S6959.json create mode 100644 its/ruling/src/test/expected/jsts/desktop/typescript-S6959.json create mode 100644 packages/jsts/src/rules/S6959/cb.fixture.ts create mode 100644 packages/jsts/src/rules/S6959/cb.test.ts create mode 100644 packages/jsts/src/rules/S6959/index.ts create mode 100644 packages/jsts/src/rules/S6959/rule.ts create mode 100644 packages/jsts/src/rules/S6959/unit.test.ts create mode 100644 sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ReduceInitialValueCheck.java create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.html create mode 100644 sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.json diff --git a/its/ruling/src/test/expected/jsts/ant-design/typescript-S6959.json b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6959.json new file mode 100644 index 00000000000..91e3c8fd583 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/ant-design/typescript-S6959.json @@ -0,0 +1,5 @@ +{ +"ant-design:components/anchor/Anchor.tsx": [ +189 +] +} diff --git a/its/ruling/src/test/expected/jsts/desktop/typescript-S6959.json b/its/ruling/src/test/expected/jsts/desktop/typescript-S6959.json new file mode 100644 index 00000000000..159ce5fd265 --- /dev/null +++ b/its/ruling/src/test/expected/jsts/desktop/typescript-S6959.json @@ -0,0 +1,11 @@ +{ +"desktop:app/src/lib/parse-carriage-return.ts": [ +30 +], +"desktop:app/src/main-process/menu/build-default-menu.ts": [ +644 +], +"desktop:app/src/ui/donut.tsx": [ +60 +] +} diff --git a/packages/jsts/src/rules/S6959/cb.fixture.ts b/packages/jsts/src/rules/S6959/cb.fixture.ts new file mode 100644 index 00000000000..0810c1f3961 --- /dev/null +++ b/packages/jsts/src/rules/S6959/cb.fixture.ts @@ -0,0 +1,13 @@ +function noncompliant(xs: number[]) { + return xs.reduce((acc, x) => acc + x); // Noncompliant {{Add an initial value to this "reduce()" call.}} +} + +function compliant(xs: number[]) { + return xs.reduce((acc, x) => acc + x, 0); // Compliant +} + +function coverage(x: any) { + x.m(); + x.reduce(); + x.reduce(42); +} diff --git a/packages/jsts/src/rules/S6959/cb.test.ts b/packages/jsts/src/rules/S6959/cb.test.ts new file mode 100644 index 00000000000..7be0e126709 --- /dev/null +++ b/packages/jsts/src/rules/S6959/cb.test.ts @@ -0,0 +1,28 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2024 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 { check } from '../tools'; +import { rule } from './'; +import path from 'path'; + +const sonarId = path.basename(__dirname); + +describe('Rule S6959', () => { + check(sonarId, rule, __dirname); +}); diff --git a/packages/jsts/src/rules/S6959/index.ts b/packages/jsts/src/rules/S6959/index.ts new file mode 100644 index 00000000000..da2121d0940 --- /dev/null +++ b/packages/jsts/src/rules/S6959/index.ts @@ -0,0 +1,20 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2024 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. + */ +export { rule } from './rule'; diff --git a/packages/jsts/src/rules/S6959/rule.ts b/packages/jsts/src/rules/S6959/rule.ts new file mode 100644 index 00000000000..b2321b89003 --- /dev/null +++ b/packages/jsts/src/rules/S6959/rule.ts @@ -0,0 +1,48 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2024 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/S6959/javascript + +import { Rule } from 'eslint'; +import { isArray, isCallingMethod, isRequiredParserServices } from '../helpers'; + +export const rule: Rule.RuleModule = { + meta: { + messages: { + message: 'Add an initial value to this "reduce()" call.', + }, + }, + create(context: Rule.RuleContext) { + const services = context.parserServices; + if (!isRequiredParserServices(services)) { + return {}; + } + + return { + CallExpression(node) { + if (isCallingMethod(node, 1, 'reduce') && isArray(node.callee.object, services)) { + context.report({ + node: node.callee.property, + messageId: 'message', + }); + } + }, + }; + }, +}; diff --git a/packages/jsts/src/rules/S6959/unit.test.ts b/packages/jsts/src/rules/S6959/unit.test.ts new file mode 100644 index 00000000000..266419e27e4 --- /dev/null +++ b/packages/jsts/src/rules/S6959/unit.test.ts @@ -0,0 +1,31 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2024 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 { rule } from './'; + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); +ruleTester.run('"Array.reduce()" calls should include an initial value', rule, { + valid: [ + { + code: 'xs.reduce((acc, x) => acc + x);', + }, + ], + invalid: [], +}); diff --git a/packages/jsts/src/rules/index.ts b/packages/jsts/src/rules/index.ts index 38148d4aab3..363ee84736c 100644 --- a/packages/jsts/src/rules/index.ts +++ b/packages/jsts/src/rules/index.ts @@ -244,6 +244,7 @@ import { rule as S4507 } from './S4507'; // production-debug import { rule as S2245 } from './S2245'; // pseudo-random import { rule as S1444 } from './S1444'; // public-static-readonly import { rule as S5443 } from './S5443'; // publicly-writable-directories +import { rule as S6959 } from './S6959'; // reduce-initial-value import { rule as S6564 } from './S6564'; // redundant-type-aliases import { rule as S5843 } from './S5843'; // regex-complexity import { rule as S4784 } from './S4784'; // regular-expr @@ -534,6 +535,7 @@ rules['production-debug'] = S4507; rules['pseudo-random'] = S2245; rules['public-static-readonly'] = S1444; rules['publicly-writable-directories'] = S5443; +rules['reduce-initial-value'] = S6959; rules['redundant-type-aliases'] = S6564; rules['regex-complexity'] = S5843; rules['regular-expr'] = S4784; 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 a716d034f60..1d34bcfc5b7 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 @@ -394,6 +394,7 @@ public static List> getAllChecks() { PubliclyWritableDirectoriesCheck.class, ReassignedParameterCheck.class, RedeclaredSymbolCheck.class, + ReduceInitialValueCheck.class, RedundantAssignmentCheck.class, RedundantTypeAliasesCheck.class, ReferenceErrorCheck.class, diff --git a/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ReduceInitialValueCheck.java b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ReduceInitialValueCheck.java new file mode 100644 index 00000000000..e4a2cff0aa8 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/java/org/sonar/javascript/checks/ReduceInitialValueCheck.java @@ -0,0 +1,36 @@ +/* + * SonarQube JavaScript Plugin + * Copyright (C) 2011-2024 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 = "S6959") +public class ReduceInitialValueCheck implements EslintBasedCheck { + + @Override + public String eslintKey() { + return "reduce-initial-value"; + } +} diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.html b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.html new file mode 100644 index 00000000000..d2ca31ad0ab --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.html @@ -0,0 +1,32 @@ +

Why is this an issue?

+

The Array.prototype.reduce() method in JavaScript is used to apply a function against an accumulator and each element in the array +(from left to right) to reduce it to a single output value. It is a convenient method that can simplify logic in your code.

+

However, it’s important to always provide an initial value as the second argument to reduce(). The initial value is used as the first +argument to the first call of the callback function. If no initial value is supplied, JavaScript will use the first element of the array as the +initial accumulator value and start iterating at the second element.

+

This can lead to runtime errors if the array is empty, as reduce() will throw a TypeError.

+
+function sum(xs) {
+  return xs.reduce((acc, current) => acc + current); // Noncompliant
+}
+console.log(sum([1, 2, 3, 4, 5])); // Prints 15
+console.log(sum([])); // TypeError: Reduce of empty array with no initial value
+
+

To fix this, always provide an initial value as the second argument to reduce().

+
+function sum(xs) {
+  return xs.reduce((acc, current) => acc + current, 0);
+}
+console.log(sum([1, 2, 3, 4, 5])); // Prints 15
+console.log(sum([])); // Prints 0
+
+

Resources

+

Documentation

+ + diff --git a/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.json b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.json new file mode 100644 index 00000000000..a7903a144b7 --- /dev/null +++ b/sonar-plugin/javascript-checks/src/main/resources/org/sonar/l10n/javascript/rules/javascript/S6959.json @@ -0,0 +1,27 @@ +{ + "title": "\"Array.reduce()\" calls should include an initial value", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "type-dependent" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6959", + "sqKey": "S6959", + "scope": "All", + "quickfix": "targeted", + "code": { + "impacts": { + "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 7e461a716d7..4d7d2a017d2 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 @@ -327,6 +327,7 @@ "S6854", "S6855", "S6859", - "S6861" + "S6861", + "S6959" ] }