eslint-plugin-sonarjs
Advanced tools
Comparing version 0.23.0 to 0.24.0
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
140
314206
30
4537