Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eslint-plugin-sonarjs

Package Overview
Dependencies
Maintainers
5
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-sonarjs - npm Package Compare versions

Comparing version 0.23.0 to 0.24.0

204

lib/rules/cognitive-complexity.js

@@ -50,35 +50,11 @@ "use strict";

create(context) {
const threshold = typeof context.options[0] === 'number' ? context.options[0] : DEFAULT_THRESHOLD;
const { options } = context;
/** Complexity threshold */
const threshold = typeof options[0] === 'number' ? options[0] : DEFAULT_THRESHOLD;
/** Indicator if the file complexity should be reported */
const isFileComplexity = context.options.includes('metric');
/** Complexity of the file */
let fileComplexity = 0;
/** Complexity of the current function if it is *not* considered nested to the first level function */
let complexityIfNotNested = [];
/** Complexity of the current function if it is considered nested to the first level function */
let complexityIfNested = [];
/** Current nesting level (number of enclosing control flow statements and functions) */
let nesting = 0;
/** Indicator if the current top level function has a structural (generated by control flow statements) complexity */
let topLevelHasStructuralComplexity = false;
/** Indicator if the current top level function is React functional component */
const reactFunctionalComponent = {
nameStartsWithCapital: false,
returnsJsx: false,
isConfirmed() {
return this.nameStartsWithCapital && this.returnsJsx;
},
init(node) {
this.nameStartsWithCapital = nameStartsWithCapital(node);
this.returnsJsx = false;
},
};
/** Own (not including nested functions) complexity of the current top function */
let topLevelOwnComplexity = [];
/** Nodes that should increase nesting level */
const nestingNodes = new Set();
/** Set of already considered (with already computed complexity) logical expressions */
const consideredLogicalExpressions = new Set();
/** Stack of enclosing functions */
const enclosingFunctions = [];
let secondLevelFunctions = [];
/** Stack of scopes that are either functions or the program */
const scopes = [];
return {

@@ -92,16 +68,22 @@ ':function': (node) => {

'*'(node) {
if (nestingNodes.has(node)) {
nesting++;
if (scopes[scopes.length - 1]?.nestingNodes.has(node)) {
scopes[scopes.length - 1].nestingLevel++;
}
},
'*:exit'(node) {
if (nestingNodes.has(node)) {
nesting--;
nestingNodes.delete(node);
if (scopes[scopes.length - 1]?.nestingNodes.has(node)) {
scopes[scopes.length - 1].nestingLevel--;
scopes[scopes.length - 1].nestingNodes.delete(node);
}
},
Program() {
fileComplexity = 0;
Program(node) {
scopes.push({
node,
nestingLevel: 0,
nestingNodes: new Set(),
complexityPoints: [],
});
},
'Program:exit'(node) {
const programComplexity = scopes.pop();
if (isFileComplexity) {

@@ -112,3 +94,5 @@ // value from the message will be saved in SonarQube as file complexity metric

messageId: 'fileComplexity',
data: { complexityAmount: fileComplexity },
data: {
complexityAmount: programComplexity.complexityPoints.reduce((acc, cur) => acc + cur.complexity, 0),
},
});

@@ -153,57 +137,9 @@ }

},
ReturnStatement(node) {
visitReturnStatement(node);
},
};
function onEnterFunction(node) {
if (enclosingFunctions.length === 0) {
// top level function
topLevelHasStructuralComplexity = false;
reactFunctionalComponent.init(node);
topLevelOwnComplexity = [];
secondLevelFunctions = [];
}
else if (enclosingFunctions.length === 1) {
// second level function
complexityIfNotNested = [];
complexityIfNested = [];
}
else {
nesting++;
nestingNodes.add(node);
}
enclosingFunctions.push(node);
scopes.push({ node, nestingLevel: 0, nestingNodes: new Set(), complexityPoints: [] });
}
function onLeaveFunction(node) {
enclosingFunctions.pop();
if (enclosingFunctions.length === 0) {
// top level function
if (topLevelHasStructuralComplexity && !reactFunctionalComponent.isConfirmed()) {
let totalComplexity = topLevelOwnComplexity;
secondLevelFunctions.forEach(secondLevelFunction => {
totalComplexity = totalComplexity.concat(secondLevelFunction.complexityIfNested);
});
checkFunction(totalComplexity, (0, locations_1.getMainFunctionTokenLocation)(node, node.parent, context));
}
else {
checkFunction(topLevelOwnComplexity, (0, locations_1.getMainFunctionTokenLocation)(node, node.parent, context));
secondLevelFunctions.forEach(secondLevelFunction => {
checkFunction(secondLevelFunction.complexityIfThisSecondaryIsTopLevel, (0, locations_1.getMainFunctionTokenLocation)(secondLevelFunction.node, secondLevelFunction.parent, context));
});
}
}
else if (enclosingFunctions.length === 1) {
// second level function
secondLevelFunctions.push({
node,
parent: node.parent,
complexityIfNested,
complexityIfThisSecondaryIsTopLevel: complexityIfNotNested,
loc: (0, locations_1.getMainFunctionTokenLocation)(node, node.parent, context),
});
}
else {
// complexity of third+ level functions is computed in their parent functions
// so we never raise an issue for them
}
const functionComplexity = scopes.pop();
checkFunction(functionComplexity.complexityPoints, (0, locations_1.getMainFunctionTokenLocation)(node, node.parent, context));
}

@@ -221,3 +157,3 @@ function visitIfStatement(ifStatement) {

// always increase nesting level inside `then` statement
nestingNodes.add(ifStatement.consequent);
scopes[scopes.length - 1].nestingNodes.add(ifStatement.consequent);
// if `else` branch is not `else if` then

@@ -227,3 +163,3 @@ // - increase nesting level inside `else` statement

if (ifStatement.alternate && !(0, nodes_1.isIfStatement)(ifStatement.alternate)) {
nestingNodes.add(ifStatement.alternate);
scopes[scopes.length - 1].nestingNodes.add(ifStatement.alternate);
const elseTokenLoc = (0, locations_1.getFirstTokenAfter)(ifStatement.consequent, context).loc;

@@ -235,3 +171,3 @@ addComplexity(elseTokenLoc);

addStructuralComplexity((0, locations_1.getFirstToken)(loop, context).loc);
nestingNodes.add(loop.body);
scopes[scopes.length - 1].nestingNodes.add(loop.body);
}

@@ -241,3 +177,3 @@ function visitSwitchStatement(switchStatement) {

for (const switchCase of switchStatement.cases) {
nestingNodes.add(switchCase);
scopes[scopes.length - 1].nestingNodes.add(switchCase);
}

@@ -252,3 +188,3 @@ }

addStructuralComplexity((0, locations_1.getFirstToken)(catchClause, context).loc);
nestingNodes.add(catchClause.body);
scopes[scopes.length - 1].nestingNodes.add(catchClause.body);
}

@@ -258,27 +194,5 @@ function visitConditionalExpression(conditionalExpression) {

addStructuralComplexity(questionTokenLoc);
nestingNodes.add(conditionalExpression.consequent);
nestingNodes.add(conditionalExpression.alternate);
scopes[scopes.length - 1].nestingNodes.add(conditionalExpression.consequent);
scopes[scopes.length - 1].nestingNodes.add(conditionalExpression.alternate);
}
function visitReturnStatement({ argument }) {
// top level function
if (enclosingFunctions.length === 1 &&
argument &&
['JSXElement', 'JSXFragment'].includes(argument.type)) {
reactFunctionalComponent.returnsJsx = true;
}
}
function nameStartsWithCapital(node) {
const checkFirstLetter = (name) => {
const firstLetter = name[0];
return firstLetter === firstLetter.toUpperCase();
};
if (!(0, nodes_1.isArrowFunctionExpression)(node) && node.id) {
return checkFirstLetter(node.id.name);
}
const { parent } = node;
if (parent && parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
return checkFirstLetter(parent.id.name);
}
return false;
}
function visitLogicalExpression(logicalExpression) {

@@ -290,2 +204,5 @@ const jsxShortCircuitNodes = (0, jsx_1.getJsxShortCircuitNodes)(logicalExpression);

}
if (isDefaultValuePattern(logicalExpression)) {
return;
}
if (!consideredLogicalExpressions.has(logicalExpression)) {

@@ -303,2 +220,19 @@ const flattenedLogicalExpressions = flattenLogicalExpression(logicalExpression);

}
function isDefaultValuePattern(node) {
const { left, right, operator, parent } = node;
const operators = ['||', '??'];
const literals = ['Literal', 'ArrayExpression', 'ObjectExpression'];
switch (parent?.type) {
/* Matches: const x = a || literal */
case 'VariableDeclarator':
return operators.includes(operator) && literals.includes(right.type);
/* Matches: a = a || literal */
case 'AssignmentExpression':
return (operators.includes(operator) &&
literals.includes(right.type) &&
context.getSourceCode().getText(parent.left) === context.getSourceCode().getText(left));
default:
return false;
}
}
function flattenLogicalExpression(node) {

@@ -316,41 +250,15 @@ if ((0, nodes_1.isLogicalExpression)(node)) {

function addStructuralComplexity(location) {
const added = nesting + 1;
const added = scopes[scopes.length - 1].nestingLevel + 1;
const complexityPoint = { complexity: added, location };
if (enclosingFunctions.length === 0) {
// top level scope
fileComplexity += added;
}
else if (enclosingFunctions.length === 1) {
// top level function
topLevelHasStructuralComplexity = true;
topLevelOwnComplexity.push(complexityPoint);
}
else {
// second+ level function
complexityIfNested.push({ complexity: added + 1, location });
complexityIfNotNested.push(complexityPoint);
}
scopes[scopes.length - 1].complexityPoints.push(complexityPoint);
}
function addComplexity(location) {
const complexityPoint = { complexity: 1, location };
if (enclosingFunctions.length === 0) {
// top level scope
fileComplexity += 1;
}
else if (enclosingFunctions.length === 1) {
// top level function
topLevelOwnComplexity.push(complexityPoint);
}
else {
// second+ level function
complexityIfNested.push(complexityPoint);
complexityIfNotNested.push(complexityPoint);
}
scopes[scopes.length - 1].complexityPoints.push(complexityPoint);
}
function checkFunction(complexity = [], loc) {
const complexityAmount = complexity.reduce((acc, cur) => acc + cur.complexity, 0);
fileComplexity += complexityAmount;
if (isFileComplexity) {
return;
}
const complexityAmount = complexity.reduce((acc, cur) => acc + cur.complexity, 0);
if (complexityAmount > threshold) {

@@ -357,0 +265,0 @@ const secondaryLocations = complexity.map(complexityPoint => {

{
"name": "eslint-plugin-sonarjs",
"version": "0.23.0",
"version": "0.24.0",
"description": "SonarJS rules for ESLint",

@@ -19,14 +19,13 @@ "main": "lib/index.js",

"engines": {
"node": ">=14"
"node": ">=16"
},
"scripts": {
"build": "rimraf lib && npm run check-format && tsc -d -p tsconfig-src.json",
"build": "rimraf lib && tsc -d -p tsconfig.json",
"test": "jest",
"ruling": "ts-node --files ruling/index.ts",
"typecheck": "tsc -p tsconfig.json",
"lint": "eslint --ext js,ts src tests ruling/index.ts",
"precommit": "lint-staged && npm run typecheck",
"precommit": "pretty-quick --staged",
"prepare": "husky install .husky",
"prepack": "npm run build",
"format": "prettier --write \"{src,tests}/**/*.ts\"",
"check-format": "prettier --list-different \"{src,tests}/**/*.ts\""
"format": "prettier --write .",
"check-format": "prettier --list-different ."
},

@@ -55,9 +54,10 @@ "peerDependencies": {

"eslint-plugin-notice": "0.9.10",
"eslint-plugin-sonarjs": "0.22.0",
"eslint-plugin-sonarjs": "0.23.0",
"husky": "8.0.3",
"jest": "29.5.0",
"jest-sonar-reporter": "2.0.0",
"lint-staged": "13.0.3",
"lodash": "4.17.21",
"minimist": "1.2.6",
"prettier": "2.7.1",
"pretty-quick": "3.1.3",
"rimraf": "3.0.2",

@@ -99,14 +99,3 @@ "ts-jest": "29.1.1",

]
},
"lint-staged": {
"*.{ts,tsx,js}": [
"eslint",
"prettier --write",
"git add"
],
"*.json": [
"prettier --write",
"git add"
]
}
}

@@ -11,12 +11,12 @@ # eslint-plugin-sonarjs [![npm version](https://badge.fury.io/js/eslint-plugin-sonarjs.svg)](https://badge.fury.io/js/eslint-plugin-sonarjs) [![Build Status](https://api.cirrus-ci.com/github/SonarSource/eslint-plugin-sonarjs.svg?branch=master)](https://cirrus-ci.com/github/SonarSource/eslint-plugin-sonarjs) [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=eslint-plugin-sonarjs&metric=alert_status)](https://sonarcloud.io/dashboard?id=eslint-plugin-sonarjs) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=eslint-plugin-sonarjs&metric=coverage)](https://sonarcloud.io/dashboard?id=eslint-plugin-sonarjs)

* All branches in a conditional structure should not have exactly the same implementation ([`no-all-duplicated-branches`])
* Collection elements should not be replaced unconditionally ([`no-element-overwrite`])
* Empty collections should not be accessed or iterated ([`no-empty-collection`])
* Function calls should not pass extra arguments ([`no-extra-arguments`])
* Related "if/else if" statements should not have the same condition ([`no-identical-conditions`])
* Identical expressions should not be used on both sides of a binary operator ([`no-identical-expressions`])
* Return values from functions without side effects should not be ignored ([`no-ignored-return`]) (*uses-types*)
* Loops with at most one iteration should be refactored ([`no-one-iteration-loop`])
* The output of functions that don't return anything should not be used ([`no-use-of-empty-return-value`])
* Non-existent operators '=+', '=-' and '=!' should not be used ([`non-existent-operator`]) (:wrench: *fixable*)
- All branches in a conditional structure should not have exactly the same implementation ([`no-all-duplicated-branches`])
- Collection elements should not be replaced unconditionally ([`no-element-overwrite`])
- Empty collections should not be accessed or iterated ([`no-empty-collection`])
- Function calls should not pass extra arguments ([`no-extra-arguments`])
- Related "if/else if" statements should not have the same condition ([`no-identical-conditions`])
- Identical expressions should not be used on both sides of a binary operator ([`no-identical-expressions`])
- Return values from functions without side effects should not be ignored ([`no-ignored-return`]) (_uses-types_)
- Loops with at most one iteration should be refactored ([`no-one-iteration-loop`])
- The output of functions that don't return anything should not be used ([`no-use-of-empty-return-value`])
- Non-existent operators '=+', '=-' and '=!' should not be used ([`non-existent-operator`]) (:wrench: _fixable_)

@@ -27,24 +27,24 @@ ### Code Smell Detection :pig:

* Cognitive Complexity of functions should not be too high ([`cognitive-complexity`])
* "if ... else if" constructs should end with "else" clauses ([`elseif-without-else`]) (*disabled*)
* "switch" statements should not have too many "case" clauses ([`max-switch-cases`])
* Collapsible "if" statements should be merged ([`no-collapsible-if`])
* Collection sizes and array length comparisons should make sense ([`no-collection-size-mischeck`]) (:wrench: *fixable*, *uses-types*)
* String literals should not be duplicated ([`no-duplicate-string`])
* Two branches in a conditional structure should not have exactly the same implementation ([`no-duplicated-branches`])
* Boolean expressions should not be gratuitous ([`no-gratuitous-expressions`])
* Functions should not have identical implementations ([`no-identical-functions`])
* Boolean checks should not be inverted ([`no-inverted-boolean-check`]) (:wrench: *fixable*, *disabled*)
* "switch" statements should not be nested ([`no-nested-switch`])
* Template literals should not be nested ([`no-nested-template-literals`])
* Boolean literals should not be redundant ([`no-redundant-boolean`])
* Jump statements should not be redundant ([`no-redundant-jump`]) (:wrench: *fixable*)
* Conditionals should start on new lines ([`no-same-line-conditional`]) (:wrench: *fixable*)
* "switch" statements should have at least 3 "case" clauses ([`no-small-switch`])
* Collection and array contents should be used ([`no-unused-collection`])
* "catch" clauses should do more than rethrow ([`no-useless-catch`])
* Local variables should not be declared and then immediately returned or thrown ([`prefer-immediate-return`]) (:wrench: *fixable*)
* Object literal syntax should be used ([`prefer-object-literal`])
* Return of boolean expressions should not be wrapped into an "if-then-else" statement ([`prefer-single-boolean-return`]) (:wrench: *fixable*)
* A "while" loop should be used instead of a "for" loop ([`prefer-while`]) (:wrench: *fixable*)
- Cognitive Complexity of functions should not be too high ([`cognitive-complexity`])
- "if ... else if" constructs should end with "else" clauses ([`elseif-without-else`]) (_disabled_)
- "switch" statements should not have too many "case" clauses ([`max-switch-cases`])
- Collapsible "if" statements should be merged ([`no-collapsible-if`])
- Collection sizes and array length comparisons should make sense ([`no-collection-size-mischeck`]) (:wrench: _fixable_, _uses-types_)
- String literals should not be duplicated ([`no-duplicate-string`])
- Two branches in a conditional structure should not have exactly the same implementation ([`no-duplicated-branches`])
- Boolean expressions should not be gratuitous ([`no-gratuitous-expressions`])
- Functions should not have identical implementations ([`no-identical-functions`])
- Boolean checks should not be inverted ([`no-inverted-boolean-check`]) (:wrench: _fixable_, _disabled_)
- "switch" statements should not be nested ([`no-nested-switch`])
- Template literals should not be nested ([`no-nested-template-literals`])
- Boolean literals should not be redundant ([`no-redundant-boolean`])
- Jump statements should not be redundant ([`no-redundant-jump`]) (:wrench: _fixable_)
- Conditionals should start on new lines ([`no-same-line-conditional`]) (:wrench: _fixable_)
- "switch" statements should have at least 3 "case" clauses ([`no-small-switch`])
- Collection and array contents should be used ([`no-unused-collection`])
- "catch" clauses should do more than rethrow ([`no-useless-catch`])
- Local variables should not be declared and then immediately returned or thrown ([`prefer-immediate-return`]) (:wrench: _fixable_)
- Object literal syntax should be used ([`prefer-object-literal`])
- Return of boolean expressions should not be wrapped into an "if-then-else" statement ([`prefer-single-boolean-return`]) (:wrench: _fixable_)
- A "while" loop should be used instead of a "for" loop ([`prefer-while`]) (:wrench: _fixable_)

@@ -86,9 +86,9 @@ [`cognitive-complexity`]: ./docs/rules/cognitive-complexity.md

* Node.js (>=12.x).
* ESLint 5.x, 6.x, 7.x or 8.x (peer dependency for the plugin).
- Node.js (>=16.x).
- ESLint 5.x, 6.x, 7.x or 8.x (peer dependency for the plugin).
## Usage
* If you don't have ESLint yet configured for your project, follow [these instructions](https://github.com/eslint/eslint#installation-and-usage).
* Install `eslint-plugin-sonarjs` using `npm` (or `yarn`) for your project or globally:
- If you don't have ESLint yet configured for your project, follow [these instructions](https://github.com/eslint/eslint#installation-and-usage).
- Install `eslint-plugin-sonarjs` using `npm` (or `yarn`) for your project or globally:

@@ -100,3 +100,3 @@ ```sh

* Add `eslint-plugin-sonarjs` to the `plugins` option of your `.eslintrc`:
- Add `eslint-plugin-sonarjs` to the `plugins` option of your `.eslintrc`:

@@ -109,3 +109,3 @@ ```json

* Add `plugin:sonarjs/recommended` to the `extends` option to enable all recommended rules:
- Add `plugin:sonarjs/recommended` to the `extends` option to enable all recommended rules:

@@ -118,3 +118,3 @@ ```json

* or enable only some rules manually:
- or enable only some rules manually:

@@ -130,11 +130,12 @@ ```javascript

```
* To enable all rules of this plugin, use `@typescript-eslint/parser` as a parser for ESLint ([like we do](https://github.com/SonarSource/eslint-plugin-sonarjs/blob/6e06d59a233e07b28fbbd6398e08b9b0c63b18f9/.eslintrc.js#L4)) and set the [parserOptions.project](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#parseroptionsproject) option. Thanks to it, type information is available, which is beneficial or even essential for some rules.
- To enable all rules of this plugin, use `@typescript-eslint/parser` as a parser for ESLint ([like we do](https://github.com/SonarSource/eslint-plugin-sonarjs/blob/6e06d59a233e07b28fbbd6398e08b9b0c63b18f9/.eslintrc.js#L4)) and set the [parserOptions.project](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#parseroptionsproject) option. Thanks to it, type information is available, which is beneficial or even essential for some rules.
## Available Configurations
This plugin provides only a `recommended` configuration. Almost all rules are activated in this profile with a few exceptions (check the `disabled` tag in the rules list). The `recommended` configuration activates rules with `error` severity.
This plugin provides only a `recommended` configuration. Almost all rules are activated in this profile with a few exceptions (check the `disabled` tag in the rules list). The `recommended` configuration activates rules with `error` severity.
## ESLint and Sonar
This plugin exposes to ESLint users a subset of JS/TS rules from Sonar-* products (aka [SonarJS](https://github.com/SonarSource/SonarJS)). We extracted the rules that are not available in ESLint core or other ESLint plugins to be beneficial for the ESLint community.
This plugin exposes to ESLint users a subset of JS/TS rules from Sonar-\* products (aka [SonarJS](https://github.com/SonarSource/SonarJS)). We extracted the rules that are not available in ESLint core or other ESLint plugins to be beneficial for the ESLint community.

@@ -141,0 +142,0 @@ If you are a [SonarQube](https://www.sonarqube.org) or [SonarCloud](https://sonarcloud.io) user, to lint your code locally, we suggest using [SonarLint](https://www.sonarlint.org) IDE extension (available for VSCode, JetBrains IDEs and Eclipse). You can connect SonarLint to your SonarQube/SonarCloud project to synchronize rules configuration, issue statuses, etc.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc