├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── .ncurc.js ├── .npmignore ├── .prettierignore ├── .travis.yml ├── CHANGES.md ├── LICENSE-LGPL3.0-only.txt ├── README.md ├── docs ├── CONTRIBUTING.md ├── RELEASING.md └── rules │ ├── cognitive-complexity.md │ ├── max-switch-cases.md │ ├── no-all-duplicated-branches.md │ ├── no-collapsible-if.md │ ├── no-collection-size-mischeck.md │ ├── no-duplicate-string.md │ ├── no-duplicated-branches.md │ ├── no-element-overwrite.md │ ├── no-extra-arguments.md │ ├── no-identical-conditions.md │ ├── no-identical-expressions.md │ ├── no-identical-functions.md │ ├── no-inverted-boolean-check.md │ ├── no-one-iteration-loop.md │ ├── no-redundant-boolean.md │ ├── no-redundant-jump.md │ ├── no-same-line-conditional.md │ ├── no-small-switch.md │ ├── no-unused-collection.md │ ├── no-use-of-empty-return-value.md │ ├── no-useless-catch.md │ ├── prefer-immediate-return.md │ ├── prefer-object-literal.md │ ├── prefer-single-boolean-return.md │ └── prefer-while.md ├── package.json ├── radar-project.properties ├── ruling ├── .eslintignore ├── .eslintrc.js ├── index.ts └── snapshots │ ├── cognitive-complexity │ ├── max-switch-cases │ ├── no-all-duplicated-branches │ ├── no-collapsible-if │ ├── no-duplicate-string │ ├── no-duplicated-branches │ ├── no-element-overwrite │ ├── no-extra-arguments │ ├── no-identical-conditions │ ├── no-identical-expressions │ ├── no-identical-functions │ ├── no-inverted-boolean-check │ ├── no-one-iteration-loop │ ├── no-redundant-boolean │ ├── no-redundant-jump │ ├── no-same-line-conditional │ ├── no-small-switch │ ├── no-unused-collection │ ├── no-use-of-empty-return-value │ ├── no-useless-catch │ ├── prefer-immediate-return │ ├── prefer-object-literal │ ├── prefer-single-boolean-return │ └── prefer-while ├── scripts ├── analyze.sh ├── file-header.ts ├── generate-rules-radar-meta.ts └── test-ci.sh ├── src ├── index.ts ├── rules │ ├── cognitive-complexity.ts │ ├── max-switch-cases.ts │ ├── no-all-duplicated-branches.ts │ ├── no-collapsible-if.ts │ ├── no-collection-size-mischeck.ts │ ├── no-duplicate-string.ts │ ├── no-duplicated-branches.ts │ ├── no-element-overwrite.ts │ ├── no-extra-arguments.ts │ ├── no-identical-conditions.ts │ ├── no-identical-expressions.ts │ ├── no-identical-functions.ts │ ├── no-inverted-boolean-check.ts │ ├── no-one-iteration-loop.ts │ ├── no-redundant-boolean.ts │ ├── no-redundant-jump.ts │ ├── no-same-line-conditional.ts │ ├── no-small-switch.ts │ ├── no-unused-collection.ts │ ├── no-use-of-empty-return-value.ts │ ├── no-useless-catch.ts │ ├── prefer-immediate-return.ts │ ├── prefer-object-literal.ts │ ├── prefer-single-boolean-return.ts │ └── prefer-while.ts └── utils │ ├── collections.ts │ ├── conditions.ts │ ├── equivalence.ts │ ├── locations.ts │ ├── nodes.ts │ └── parser-services.ts ├── tests ├── index.test.ts ├── resources │ ├── file.ts │ └── tsconfig.json └── rules │ ├── cognitive-complexity.test.ts │ ├── max-switch-cases.test.ts │ ├── no-all-duplicated-branches.test.ts │ ├── no-collapsible-if.test.ts │ ├── no-collection-size-mischeck.test.ts │ ├── no-duplicate-string.test.ts │ ├── no-duplicated-branches.test.ts │ ├── no-element-overwrite.test.ts │ ├── no-extra-arguments.test.ts │ ├── no-identical-conditions.test.ts │ ├── no-identical-expressions.test.ts │ ├── no-identical-functions.test.ts │ ├── no-inverted-boolean-check.test.ts │ ├── no-one-iteration-loop.test.ts │ ├── no-redundant-boolean.test.ts │ ├── no-redundant-jump.test.ts │ ├── no-same-line-conditional.test.ts │ ├── no-small-switch.test.ts │ ├── no-unused-collection.test.ts │ ├── no-use-of-empty-return-value.test.ts │ ├── no-useless-catch.test.ts │ ├── prefer-immediate-return.test.ts │ ├── prefer-object-literal.test.ts │ ├── prefer-single-boolean-return.test.ts │ └── prefer-while.test.ts ├── tsconfig-src.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig file: https://EditorConfig.org 2 | ; Install the "EditorConfig" plugin into your editor to use 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | trim_trailing_whitespace = true 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | tests/resources 2 | ruling/javascript-test-sources 3 | lib 4 | !.*.js 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | "use strict"; 21 | module.exports = { 22 | env: { es6: true, node: true, jest: true }, 23 | extends: ["eslint:recommended", "plugin:import/errors", "prettier", "plugin:radar/recommended"], 24 | parser: "@typescript-eslint/parser", 25 | parserOptions: { 26 | ecmaVersion: 2018, 27 | ecmaFeatures: { modules: true }, 28 | sourceType: "module", 29 | }, 30 | plugins: ["import", "notice", "radar"], 31 | rules: { 32 | // possible errors 33 | "for-direction": "error", 34 | "no-prototype-builtins": "error", 35 | "no-template-curly-in-string": "error", 36 | "no-unsafe-negation": "error", 37 | 38 | // best practices 39 | "array-callback-return": "error", 40 | "block-scoped-var": "error", 41 | complexity: "error", 42 | "consistent-return": "error", 43 | eqeqeq: ["error", "smart"], 44 | "guard-for-in": "error", 45 | "no-alert": "error", 46 | "no-caller": "error", 47 | "no-div-regex": "error", 48 | "no-eval": "error", 49 | "no-extend-native": "error", 50 | "no-extra-bind": "error", 51 | "no-extra-label": "error", 52 | "no-floating-decimal": "error", 53 | "no-implied-eval": "error", 54 | "no-iterator": "error", 55 | "no-labels": "error", 56 | "no-lone-blocks": "error", 57 | "no-loop-func": "error", 58 | "no-new": "error", 59 | "no-new-func": "error", 60 | "no-new-wrappers": "error", 61 | "no-proto": "error", 62 | "no-restricted-properties": "error", 63 | "no-return-assign": "error", 64 | "no-return-await": "error", 65 | "no-self-compare": "error", 66 | "no-sequences": "error", 67 | "no-throw-literal": "error", 68 | "no-unmodified-loop-condition": "error", 69 | "no-unused-expressions": "error", 70 | "no-useless-call": "error", 71 | "no-useless-concat": "error", 72 | "no-useless-escape": "error", 73 | "no-useless-return": "error", 74 | "no-void": "error", 75 | "no-with": "error", 76 | radix: "error", 77 | "require-await": "error", 78 | "wrap-iife": "error", 79 | yoda: "error", 80 | 81 | // stylistic 82 | camelcase: "warn", 83 | "consistent-this": ["warn", "that"], 84 | "func-name-matching": "error", 85 | "func-style": ["warn", "declaration", { allowArrowFunctions: true }], 86 | "lines-between-class-members": ["error", "always", { exceptAfterSingleLine: true }], 87 | "max-depth": "warn", 88 | "max-lines": ["warn", 1000], 89 | "max-params": ["warn", 4], 90 | "no-array-constructor": "warn", 91 | "no-bitwise": "warn", 92 | "no-lonely-if": "error", 93 | "no-multi-assign": "warn", 94 | "no-nested-ternary": "warn", 95 | "no-new-object": "warn", 96 | "no-underscore-dangle": "warn", 97 | "no-unneeded-ternary": "warn", 98 | "one-var": ["warn", "never"], 99 | "operator-assignment": "warn", 100 | "padding-line-between-statements": "error", 101 | 102 | // es2015 103 | "no-duplicate-imports": "error", 104 | "no-useless-computed-key": "error", 105 | "no-useless-rename": "error", 106 | "no-var": "error", 107 | "object-shorthand": "error", 108 | "prefer-arrow-callback": "error", 109 | "prefer-const": "error", 110 | "prefer-destructuring": ["warn", { object: true, array: false }], 111 | "prefer-numeric-literals": "warn", 112 | "prefer-rest-params": "warn", 113 | "prefer-spread": "warn", 114 | 115 | // disabled because of the usage of typescript-eslint-parser 116 | // https://github.com/eslint/typescript-eslint-parser/issues/77 117 | "no-undef": "off", 118 | "no-unused-vars": "off", 119 | 120 | // import 121 | "import/extensions": "error", 122 | "import/first": "error", 123 | "import/newline-after-import": "error", 124 | "import/no-absolute-path": "error", 125 | "import/no-amd": "error", 126 | "import/no-deprecated": "error", 127 | "import/no-duplicates": "error", 128 | "import/no-mutable-exports": "error", 129 | "import/no-named-as-default": "error", 130 | "import/no-named-as-default-member": "error", 131 | "import/no-named-default": "error", 132 | "import/order": [ 133 | "error", 134 | { 135 | groups: ["builtin", "external", ["index", "sibling"], ["parent", "internal"]], 136 | "newlines-between": "never", 137 | }, 138 | ], 139 | 140 | // does not properly work with ts 141 | "import/no-unresolved": "off", 142 | 143 | // notice 144 | "notice/notice": ["error", { templateFile: "scripts/file-header.ts" }], 145 | 146 | // radar 147 | "radar/cognitive-complexity": "warn", 148 | }, 149 | }; 150 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **I want to request a feature.** 2 | 3 | 6 | 7 | 8 | 9 | **I want to report a bug.** 10 | 11 | **Reproducer** 12 | 13 | ```javascript 14 | // minimal reproducer if relevant 15 | console.log("Hello, world"); // False-positive here 16 | ``` 17 | 18 | **Expected behavior** 19 | 20 | 21 | 22 | **I want to provide the following feedback.** 23 | 24 | **Example** 25 | 26 | 27 | 28 | **eslint-plugin-radar version:**
29 | **eslint version:**
30 | **Node.js version:**
31 | 32 | **Rule key:** 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Coverage directory used by tools like istanbul 9 | coverage 10 | 11 | # Test reports 12 | test-report.xml 13 | 14 | # Dependency directories 15 | node_modules/ 16 | 17 | # Optional npm cache directory 18 | .npm 19 | 20 | # Optional eslint cache 21 | .eslintcache 22 | 23 | # Output of 'npm pack' 24 | *.tgz 25 | 26 | # Yarn Integrity file 27 | .yarn-integrity 28 | 29 | # radar-scanner 30 | .radar 31 | .radarlint 32 | .scannerwork 33 | 34 | # generated 35 | lib 36 | 37 | # editors 38 | .idea 39 | .vscode 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ruling/javascript-test-sources"] 2 | path = ruling/javascript-test-sources 3 | url = https://github.com/SonarCommunity/javascript-test-sources.git 4 | -------------------------------------------------------------------------------- /.ncurc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | "use strict"; 21 | 22 | module.exports = { 23 | // Whitelist all for `npm-check-updates` checking besides `peer` which 24 | // indicates somewhat older versions of `eslint` we still support even 25 | // while our devDeps point to a more recent version 26 | dep: "prod,dev,optional,bundle", 27 | }; 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | yarn-debug.log* 2 | yarn-error.log* 3 | .yarn-integrity 4 | 5 | 6 | .npm 7 | .eslintcache/ 8 | *.tgz 9 | 10 | .radar/ 11 | .radarlint/ 12 | .scannerwork/ 13 | 14 | .idea/ 15 | .vscode/ 16 | .github/ 17 | 18 | coverage/ 19 | docs/ 20 | ruling/ 21 | scripts/ 22 | src/ 23 | tests/ 24 | .gitignore 25 | .gitattributes 26 | .gitmodules 27 | .npmignore 28 | .travis.yml 29 | .eslintrc.js 30 | .editorconfig 31 | .ncurc.js 32 | radar-project.properties 33 | test-report.xml 34 | tsconfig.json 35 | tsconfig-src.json 36 | yarn.lock 37 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | ruling/javascript-test-sources/** 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | language: node_js 4 | git: 5 | depth: false 6 | node_js: 7 | - 15 8 | - 14 9 | - 12 10 | - 10 11 | script: 12 | - yarn typecheck 13 | - yarn build 14 | - ./scripts/test-ci.sh 15 | - yarn prettier --list-different "{src,tests}/**/*.{js,ts}" 16 | - yarn lint 17 | - yarn ruling 18 | after_success: 19 | - ./scripts/analyze.sh 20 | cache: yarn 21 | notifications: 22 | email: false 23 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # CHANGES for `eslint-plugin-radar` 2 | 3 | ## 0.2.1 4 | 5 | - License: Update license file name to reflect license type is specifically 6 | LGPL-3.0-only 7 | - npm: Change `license` to non-deprecated SPDX identifier, `LGPL-3.0-only` 8 | as per: 9 | ) 10 | 11 | **Dev-focused:** 12 | 13 | - Linting: Apply prettier to MD files and reapply latest to whole project 14 | - npm: Revert back to using fixed rather than self-referential version of 15 | `eslint-plugin-radar` for self-linting (was adding to file space) 16 | 17 | ## 0.2.0 18 | 19 | - Enhancement: add `meta.docs` (@Loxos) 20 | - License: Rename file name to reflect license type (LGPL-3) and add extension 21 | - Docs: Add `CHANGES.md` 22 | 23 | **Dev-focused:** 24 | 25 | - Travis: Check Node 15 26 | 27 | ## 0.1.0 28 | 29 | - Forked from 30 | - License: Add headers to hidden files 31 | - npm: Add ESLint 7 to peerDeps. 32 | - npm: Bump engines to avoid EOL versions (Node >=10) 33 | - npm: Use repository URL (accepted in npm, package.json linter doesn't 34 | complain, and allows IDEs to auto-open in browser (e.g., on cmd-O) 35 | - npm: Add recommended/optional fields, author, contributors to satisfy linter 36 | 37 | **Dev-focused:** 38 | 39 | - Optimization: Add "use strict" for CJS eslint files 40 | - Linting: Apply latest `prettier` (adding `.prettierignore` to avoid applying 41 | to test sources); apply latest `eslint-plugin-import` 42 | - Linting: Check hidden files 43 | - Linting: Check all files by default, ignoring test sources and lib 44 | - Linting: Create separate ignore file for ruling (which lints nested 45 | `node_modules`) 46 | - Testing: Ensure test-ci script runs (now that Node 8 is removed) 47 | - Travis: Bump Node versions to 12 and 14 48 | - Travis: Avoid comment about "nodejs 6" being cause of failure; the need 49 | for `jest-sonar-reporter` is due to it not being in devDeps. 50 | - Properties: Point to non-deprecated `travis-ci.com` 51 | - npm: Reorder scripts so main scripts at bottom; switch `test` to check 52 | coverage and add `jest` script for non-coverage testing 53 | - npm: Add `.ncurc.js` file to prevent `npm-check-updates` auto-updating peerDeps. 54 | - npm: Self-reference current version of sonarjs, so can apply latest linting 55 | to own repository 56 | - npm: Update `lint-staged`, removing `git add` 57 | - npm: Update devDeps. 58 | - yarn: Upgrade 59 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, thanks for taking the time to contribute! :+1: 4 | 5 | ## How Can I Contribute? 6 | 7 | Report bugs and suggest improvements. If something doesn't work well for you or can be done better, please let us know! When you are creating a new issue, fill out the issue template, the information it asks for helps us resolve issues faster. 8 | 9 | ## Create New Rule 10 | 11 | - Create a new file for the rule implementation in `src/rules`. File name should be lowercased, words must be separated by dashes (`-`). 12 | - Create a test file `.test.ts` in `test/rules`. 13 | - Add the rule to `src/index.ts`. 14 | - In folder `docs/rules` create a rule documentation file `.md` 15 | - In `README.md` add a reference to this documentation file. 16 | - Run [Ruling](#ruling) test. 17 | 18 | ## Testing 19 | 20 | To run unit tests: 21 | 22 | ``` 23 | yarn test 24 | ``` 25 | 26 | To run unit tests in watch mode: 27 | 28 | ``` 29 | yarn test --watch 30 | ``` 31 | 32 | And finally to run unit tests with coverage: 33 | 34 | ``` 35 | yarn test --coverage 36 | ``` 37 | 38 | ## Ruling 39 | 40 | The ruling test is a special integration test which launches the analysis of a large code base, 41 | and then compares those results to the set of expected issues (stored as snapshot files). 42 | To have this code base locally: 43 | 44 | ```sh 45 | git submodule update --init --recursive 46 | ``` 47 | 48 | To run the ruling test: 49 | 50 | ```sh 51 | yarn ruling 52 | yarn ruling --rule # to run ruling for a single rule 53 | yarn ruling --update # to update the snapshots 54 | yarn ruling --rule --update # it is possible to combine both options 55 | ``` 56 | 57 | ## Code Style 58 | 59 | We're using Prettier to format the code, the options are in `package.json`. 60 | -------------------------------------------------------------------------------- /docs/RELEASING.md: -------------------------------------------------------------------------------- 1 | ## Releasing npm package 2 | 3 | - Install or upgrade `np` package globally (`npm install -g np`) 4 | - Login to npm with `npm adduser` 5 | - Create new branch, e.g. `1.2.0`, add upstream 6 | - Run this to publish package `np --any-branch` 7 | 8 | N.B. As the project must be compiled to JS before publishing, we added a `prepack` npm hook to take care of this. 9 | -------------------------------------------------------------------------------- /docs/rules/cognitive-complexity.md: -------------------------------------------------------------------------------- 1 | # cognitive-complexity 2 | 3 | Cognitive Complexity is a measure of how hard the control flow of a function is to understand. Functions with high Cognitive Complexity will be difficult to maintain. 4 | 5 | ## See 6 | 7 | - [Cognitive Complexity](http://redirect.sonarsource.com/doc/cognitive-complexity.html) 8 | 9 | ## Configuration 10 | 11 | The maximum authorized complexity can be provided. Default is 15. 12 | 13 | ```json 14 | { 15 | "cognitive-complexity": "error", 16 | "cognitive-complexity": ["error", 15] 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/rules/max-switch-cases.md: -------------------------------------------------------------------------------- 1 | ## max-switch-cases 2 | 3 | When `switch` statements have large sets of `case` clauses, it is usually an attempt to map two sets of data. A real map structure would be more readable and maintainable, and should be used instead. 4 | 5 | ## Configuration 6 | 7 | This rule has a numeric option (defaulted to 30) to specify the maximum number of switch cases. 8 | 9 | ```json 10 | { 11 | "max-switch-cases": "error", 12 | "max-switch-cases": ["error", 10] 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/rules/no-all-duplicated-branches.md: -------------------------------------------------------------------------------- 1 | # no-all-duplicated-branches 2 | 3 | Having all branches in a `switch` or `if` chain with the same implementation is an error. 4 | Either a copy-paste error was made and something different should be executed, 5 | or there shouldn't be a `switch`/`if` chain at all. Note that this rule does not apply to 6 | `if` chains without else, or to `switch` without `default` clauses. 7 | 8 | ## Noncompliant Code Example 9 | 10 | ```javascript 11 | if (b == 0) { 12 | // Noncompliant 13 | doOneMoreThing(); 14 | } else { 15 | doOneMoreThing(); 16 | } 17 | 18 | let a = b === 0 ? getValue() : getValue(); // Noncompliant 19 | 20 | switch ( 21 | i // Noncompliant 22 | ) { 23 | case 1: 24 | doSomething(); 25 | break; 26 | case 2: 27 | doSomething(); 28 | break; 29 | case 3: 30 | doSomething(); 31 | break; 32 | default: 33 | doSomething(); 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/rules/no-collapsible-if.md: -------------------------------------------------------------------------------- 1 | # no-collapsible-if 2 | 3 | Merging collapsible if statements increases the code's readability. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | if (x != undefined) { 9 | if (y === 2) { 10 | // ... 11 | } 12 | } 13 | ``` 14 | 15 | ## Compliant Solution 16 | 17 | ```javascript 18 | if (x != undefined && y === 2) { 19 | // ... 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/rules/no-collection-size-mischeck.md: -------------------------------------------------------------------------------- 1 | # no-collection-size-mischeck 2 | 3 | The size of a collection and the length of an array are always greater than or equal to zero. So testing that a size or length is greater than or equal to zero doesn't make sense, since the result is always `true`. Similarly testing that it is less than zero will always return `false`. Perhaps the intent was to check the non-emptiness of the collection or array instead. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | if (someSet.size >= 0) {...} // Noncompliant 9 | 10 | if (someMap.size < 0) {...} // Noncompliant 11 | 12 | const result = someArray.length >= 0; // Noncompliant 13 | ``` 14 | 15 | ## Compliant Solution 16 | 17 | ```javascript 18 | if (someSet.size > 0) {...} 19 | 20 | if (someMap.size == 0) {...} 21 | 22 | const result = someArray.length > 0; 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/rules/no-duplicate-string.md: -------------------------------------------------------------------------------- 1 | # no-duplicate-string 2 | 3 | Duplicated string literals make the process of refactoring error-prone, since you must be sure to update all occurrences. 4 | On the other hand, constants can be referenced from many places, but only need to be updated in a single place. 5 | 6 | ## Exceptions 7 | 8 | To prevent generating some false-positives, literals having less than 10 characters are excluded as well as literals matching /^\w\*$/. String literals inside import/export statements are also ignored. The same goes for statement-like string literals, e.g. `'use strict';` 9 | 10 | ## Configuration 11 | 12 | Number of times a literal must be duplicated to trigger an issue. Default is 3. 13 | 14 | ```json 15 | { 16 | "no-duplicate-string": "error", 17 | "no-duplicate-string": ["error", 5] 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/rules/no-duplicated-branches.md: -------------------------------------------------------------------------------- 1 | # no-duplicated-branches 2 | 3 | Having two `case`s in a `switch` statement or two branches in an `if` chain with the same implementation is at best 4 | duplicate code, and at worst a coding error. If the same logic is truly needed for both instances, then in an `if` 5 | chain they should be combined, or for a `switch`, one should fall through to the other. 6 | 7 | ## Noncompliant Code Example 8 | 9 | ```javascript 10 | switch (i) { 11 | case 1: 12 | doFirstThing(); 13 | doSomething(); 14 | break; 15 | case 2: 16 | doSomethingDifferent(); 17 | break; 18 | case 3: // Noncompliant; duplicates case 1's implementation 19 | doFirstThing(); 20 | doSomething(); 21 | break; 22 | default: 23 | doTheRest(); 24 | } 25 | 26 | if (a >= 0 && a < 10) { 27 | doFirstThing(); 28 | doTheThing(); 29 | } else if (a >= 10 && a < 20) { 30 | doTheOtherThing(); 31 | } else if (a >= 20 && a < 50) { 32 | // Noncompliant; duplicates first condition 33 | doFirstThing(); 34 | doTheThing(); 35 | } else { 36 | doTheRest(); 37 | } 38 | ``` 39 | 40 | ## Compliant Solution 41 | 42 | ```javascript 43 | switch (i) { 44 | case 1: 45 | case 3: 46 | doFirstThing(); 47 | doSomething(); 48 | break; 49 | case 2: 50 | doSomethingDifferent(); 51 | break; 52 | default: 53 | doTheRest(); 54 | } 55 | 56 | if ((a >= 0 && a < 10) || (a >= 20 && a < 50)) { 57 | doFirstThing(); 58 | doTheThing(); 59 | } else if (a >= 10 && a < 20) { 60 | doTheOtherThing(); 61 | } else { 62 | doTheRest(); 63 | } 64 | ``` 65 | 66 | or 67 | 68 | ```javascript 69 | switch (i) { 70 | case 1: 71 | doFirstThing(); 72 | doSomething(); 73 | break; 74 | case 2: 75 | doSomethingDifferent(); 76 | break; 77 | case 3: 78 | doFirstThing(); 79 | doThirdThing(); 80 | break; 81 | default: 82 | doTheRest(); 83 | } 84 | 85 | if (a >= 0 && a < 10) { 86 | doFirstThing(); 87 | doTheThing(); 88 | } else if (a >= 10 && a < 20) { 89 | doTheOtherThing(); 90 | } else if (a >= 20 && a < 50) { 91 | doFirstThing(); 92 | doTheThirdThing(); 93 | } else { 94 | doTheRest(); 95 | } 96 | ``` 97 | 98 | ## Exceptions 99 | 100 | Blocks in an `if` chain that contain a single line of code are ignored, as are blocks in a `switch` statement that 101 | contain a single line of code with or without a following break. 102 | -------------------------------------------------------------------------------- /docs/rules/no-element-overwrite.md: -------------------------------------------------------------------------------- 1 | # no-element-overwrite 2 | 3 | It is highly suspicious when a value is saved for a key or index and then unconditionally overwritten. Such replacements are likely in error. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | fruits[1] = "banana"; 9 | fruits[1] = "apple"; // Noncompliant - value on index 1 is overwritten 10 | 11 | myMap.set("key", 1); 12 | myMap.set("key", 2); // Noncompliant - value for key "key" is replaced 13 | 14 | mySet.add(1); 15 | mySet.add(1); // Noncompliant - element is already in the set 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/rules/no-extra-arguments.md: -------------------------------------------------------------------------------- 1 | # no-extra-arguments 2 | 3 | You can easily call a JavaScript function with more arguments than the function needs, but the extra arguments will be just ignored by function execution. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | function say(a, b) { 9 | print(a + " " + b); 10 | } 11 | 12 | say("hello", "world", "!"); // Noncompliant; last argument is not used 13 | ``` 14 | 15 | ## Exceptions 16 | 17 | No issue is reported when `arguments` is used in the body of the function being called. 18 | 19 | ```javascript 20 | function doSomething(a, b) { 21 | compute(arguments); 22 | } 23 | 24 | doSomething(1, 2, 3); // Compliant 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/rules/no-identical-conditions.md: -------------------------------------------------------------------------------- 1 | # no-identical-conditions 2 | 3 | A chain of `if`/`else if` statements is evaluated from top to bottom. At most, only 4 | one branch will be executed: the first one with a condition that evaluates to `true`. 5 | 6 | Therefore, duplicating a condition automatically leads to dead code. Usually, this is due to a 7 | copy/paste error. At best, it's simply dead code and at worst, it's a bug that is likely to induce 8 | further bugs as the code is maintained, and obviously it could lead to unexpected behavior. 9 | 10 | ## Noncompliant Code Example 11 | 12 | ```javascript 13 | if (param == 1) { 14 | openWindow(); 15 | } else if (param == 2) { 16 | closeWindow(); 17 | } else if (param == 1) { 18 | // Noncompliant 19 | moveWindowToTheBackground(); 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/rules/no-identical-expressions.md: -------------------------------------------------------------------------------- 1 | # no-identical-expressions 2 | 3 | Using the same value on either side of a binary operator is almost always a mistake. In the case 4 | of logical operators, it is either a copy/paste error and therefore a bug, or it is simply wasted 5 | code, and should be simplified. In the case of bitwise operators and most binary mathematical 6 | operators, having the same value on both sides of an operator yields predictable results, and 7 | should be simplified. 8 | 9 | This rule ignores `*`, `+`, and `=`. 10 | 11 | ## Noncompliant Code Example 12 | 13 | ```javascript 14 | if (a == b && a == b) { 15 | // if the first one is true, the second one is too 16 | doX(); 17 | } 18 | if (a > a) { 19 | // always false 20 | doW(); 21 | } 22 | 23 | var j = 5 / 5; //always 1 24 | var k = 5 - 5; //always 0 25 | ``` 26 | 27 | ## Exceptions 28 | 29 | The specific case of testing one variable against itself is a valid test for `NaN` and is therefore ignored. 30 | 31 | Similarly, left-shifting 1 onto 1 is common in the construction of bit masks, and is ignored. 32 | 33 | Moreover comma operator , and `instanceof` operator are ignored as there are use-cases when there usage is valid. 34 | 35 | ```javascript 36 | if (f !== f) { 37 | // test for NaN value 38 | console.log("f is NaN"); 39 | } 40 | 41 | var i = 1 << 1; // Compliant 42 | var j = a << a; // Noncompliant 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/rules/no-identical-functions.md: -------------------------------------------------------------------------------- 1 | # no-identical-functions 2 | 3 | When two functions have the same implementation, either it was a mistake - something else was intended - or the 4 | duplication was intentional, but may be confusing to maintainers. In the latter case, the code should be refactored. 5 | 6 | ## Noncompliant Code Example 7 | 8 | ```javascript 9 | function calculateCode() { 10 | doTheThing(); 11 | doOtherThing(); 12 | return code; 13 | } 14 | 15 | function getName() { 16 | // Noncompliant 17 | doTheThing(); 18 | doOtherThing(); 19 | return code; 20 | } 21 | ``` 22 | 23 | ## Compliant Solution 24 | 25 | ```javascript 26 | function calculateCode() { 27 | doTheThing(); 28 | doOtherThing(); 29 | return code; 30 | } 31 | 32 | function getName() { 33 | return calculateCode(); 34 | } 35 | ``` 36 | 37 | ## Exceptions 38 | 39 | Functions with fewer than 3 lines are ignored. 40 | -------------------------------------------------------------------------------- /docs/rules/no-inverted-boolean-check.md: -------------------------------------------------------------------------------- 1 | # no-inverted-boolean-check 2 | 3 | :wrench: _fixable_ 4 | 5 | It is needlessly complex to invert the result of a boolean comparison. The opposite comparison should be made instead. 6 | 7 | ## Noncompliant Code Example 8 | 9 | ```javascript 10 | if (!(a === 2)) { ... } // Noncompliant 11 | ``` 12 | 13 | ## Compliant Solution 14 | 15 | ```javascript 16 | if (a !== 2) { ... } 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/rules/no-one-iteration-loop.md: -------------------------------------------------------------------------------- 1 | # no-one-iteration-loop 2 | 3 | A loop with at most one iteration is equivalent to the use of an `if` statement to conditionally execute one piece of code. No developer expects to find such a use of a loop statement. If the initial intention of the author was really to conditionally execute one piece of code, an `if` statement should be used instead. 4 | 5 | At worst that was not the initial intention of the author and so the body of the loop should be fixed to use the nested `return`, `break` or `throw` statements in a more appropriate way. 6 | 7 | ## Noncompliant Code Example 8 | 9 | ```javascript 10 | for (int i = 0; i < 10; i++) { // noncompliant, loop only executes once 11 | console.log("i is " + i); 12 | break; 13 | } 14 | ... 15 | for (int i = 0; i < 10; i++) { // noncompliant, loop only executes once 16 | if (i == x) { 17 | break; 18 | } else { 19 | console.log("i is " + i); 20 | return; 21 | } 22 | } 23 | ``` 24 | 25 | ## Compliant Solution 26 | 27 | ```javascript 28 | for (int i = 0; i < 10; i++) { 29 | console.log("i is " + i); 30 | } 31 | ... 32 | for (int i = 0; i < 10; i++) { 33 | if (i == x) { 34 | break; 35 | } else { 36 | console.log("i is " + i); 37 | } 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/rules/no-redundant-boolean.md: -------------------------------------------------------------------------------- 1 | # no-redundant-boolean 2 | 3 | Redundant Boolean literals should be removed from expressions to improve readability. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | if (booleanMethod() == true) { 9 | /* ... */ 10 | } 11 | if (booleanMethod() == false) { 12 | /* ... */ 13 | } 14 | if (booleanMethod() || false) { 15 | /* ... */ 16 | } 17 | doSomething(!false); 18 | doSomething(booleanMethod() == true); 19 | ``` 20 | 21 | ## Compliant Solution 22 | 23 | ```javascript 24 | if (booleanMethod()) { 25 | /* ... */ 26 | } 27 | if (!booleanMethod()) { 28 | /* ... */ 29 | } 30 | if (booleanMethod()) { 31 | /* ... */ 32 | } 33 | doSomething(true); 34 | doSomething(booleanMethod()); 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/rules/no-redundant-jump.md: -------------------------------------------------------------------------------- 1 | # no-redundant-jump 2 | 3 | Jump statements, such as `return`, `break` and `continue` let you change the default flow of program execution, but jump statements that direct the control flow to the original direction are just a waste of keystrokes. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | function redundantJump(x) { 9 | if (x == 1) { 10 | console.log("x == 1"); 11 | return; // Noncompliant 12 | } 13 | } 14 | ``` 15 | 16 | ## Compliant Solution 17 | 18 | ```javascript 19 | function redundantJump(x) { 20 | if (x == 1) { 21 | console.log("x == 1"); 22 | } 23 | } 24 | ``` 25 | 26 | ## Exceptions 27 | 28 | `break` and `return` inside switch statement are ignored, because they are often used for consistency. continue with label is also ignored, because label is usually used for clarity. Also a jump statement being a single statement in a block is ignored. 29 | -------------------------------------------------------------------------------- /docs/rules/no-same-line-conditional.md: -------------------------------------------------------------------------------- 1 | # no-same-line-conditional 2 | 3 | Code is clearest when each statement has its own line. Nonetheless, it is a common pattern to combine on the same line an `if` and its resulting _then_ statement. However, when an `if` is placed on the same line as the closing `}` from a preceding _then_, _else_ or _else if_ part, it is either an error - `else` is missing - or the invitation to a future error as maintainers fail to understand that the two statements are unconnected. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | if (condition1) { 9 | // ... 10 | } 11 | if (condition2) { 12 | // Noncompliant 13 | //... 14 | } 15 | ``` 16 | 17 | ## Compliant Solution 18 | 19 | ```javascript 20 | if (condition1) { 21 | // ... 22 | } else if (condition2) { 23 | //... 24 | } 25 | ``` 26 | 27 | Or 28 | 29 | ```javascript 30 | if (condition1) { 31 | // ... 32 | } 33 | 34 | if (condition2) { 35 | //... 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/rules/no-small-switch.md: -------------------------------------------------------------------------------- 1 | # no-small-switch 2 | 3 | `switch` statements are useful when there are many different cases depending on the value of the same expression. 4 | 5 | For just one or two cases however, the code will be more readable with `if` statements. 6 | 7 | ## Noncompliant Code Example 8 | 9 | ```javascript 10 | switch (variable) { 11 | case 0: 12 | doSomething(); 13 | break; 14 | default: 15 | doSomethingElse(); 16 | break; 17 | } 18 | ``` 19 | 20 | ## Compliant Solution 21 | 22 | ```javascript 23 | if (variable == 0) { 24 | doSomething(); 25 | } else { 26 | doSomethingElse(); 27 | } 28 | ``` 29 | 30 | ## See 31 | 32 |
    33 |
  • MISRA C:2004, 15.5 - Every switch statement shall have at least one case clause.
  • 34 |
  • MISRA C++:2008, 6-4-8 - Every switch statement shall have at least one case-clause.
  • 35 |
  • MISRA C:2012, 16.6 - Every switch statement shall have at least two switch-clauses
  • 36 |
37 | -------------------------------------------------------------------------------- /docs/rules/no-unused-collection.md: -------------------------------------------------------------------------------- 1 | # no-unused-collection 2 | 3 | When a collection is populated but its contents are never used, then it is surely some kind of mistake. Either refactoring has rendered the collection moot, or an access is missing. 4 | 5 | This rule raises an issue when no methods are called on a collection other than those that add or remove values. 6 | 7 | ## Noncompliant Code Example 8 | 9 | ```javascript 10 | function getLength(a, b, c) { 11 | const strings = []; // Noncompliant 12 | strings.push(a); 13 | strings.push(b); 14 | strings.push(c); 15 | 16 | return a.length + b.length + c.length; 17 | } 18 | ``` 19 | 20 | ## Compliant Solution 21 | 22 | ```javascript 23 | function getLength(a, b, c) { 24 | return a.length + b.length + c.length; 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/rules/no-use-of-empty-return-value.md: -------------------------------------------------------------------------------- 1 | # no-use-of-empty-return-value 2 | 3 | If a function does not return anything, it makes no sense to use its output. Specifically, passing it to another function, or assigning its "result" to a variable is probably a bug because such functions return `undefined`, which is probably not what was intended. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | function foo() { 9 | console.log("Hello, World!"); 10 | } 11 | 12 | a = foo(); 13 | ``` 14 | 15 | ## Compliant Solution 16 | 17 | ```javascript 18 | function foo() { 19 | console.log("Hello, World!"); 20 | } 21 | 22 | foo(); 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/rules/no-useless-catch.md: -------------------------------------------------------------------------------- 1 | # no-useless-catch 2 | 3 | A catch clause that only rethrows the caught exception has the same effect as omitting the catch altogether and letting it bubble up automatically, but with more code and the additional detriment of leaving maintainers scratching their heads. 4 | 5 | Such clauses should either be eliminated or populated with the appropriate logic. 6 | 7 | ## Noncompliant Code Example 8 | 9 | ```javascript 10 | try { 11 | doSomething(); 12 | } catch (ex) { 13 | // Noncompliant 14 | throw ex; 15 | } 16 | ``` 17 | 18 | ## Compliant Solution 19 | 20 | ```javascript 21 | try { 22 | doSomething(); 23 | } catch (ex) { 24 | console.err(ex); 25 | throw ex; 26 | } 27 | ``` 28 | 29 | or 30 | 31 | ```javascript 32 | doSomething(); 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/rules/prefer-immediate-return.md: -------------------------------------------------------------------------------- 1 | # prefer-immediate-return 2 | 3 | :wrench: _fixable_ 4 | 5 | Declaring a variable only to immediately return or throw it is a bad practice. 6 | 7 | Some developers argue that the practice improves code readability, because it enables them to explicitly name what is being returned. However, this variable is an internal implementation detail that is not exposed to the callers of the method. The method name should be sufficient for callers to know exactly what will be returned. 8 | 9 | ## Noncompliant Code Example 10 | 11 | ```javascript 12 | function ms(hours, minutes, seconds) { 13 | const duration = ((hours * 60 + minutes) * 60 + seconds) * 1000; 14 | return duration; 15 | } 16 | ``` 17 | 18 | ## Compliant Solution 19 | 20 | ```javascript 21 | function ms(hours, minutes, seconds) { 22 | return ((hours * 60 + minutes) * 60 + seconds) * 1000; 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/rules/prefer-object-literal.md: -------------------------------------------------------------------------------- 1 | # prefer-object-literal 2 | 3 | Object literal syntax, which initializes an object's properties inside the object declaration is cleaner and clearer than the alternative: creating an empty object, and then giving it properties one by one. 4 | 5 | An issue is raised when the following pattern is met: 6 | 7 | - An empty object is created. 8 | - A consecutive single-line statement adds a property to the created object. 9 | 10 | ## Noncompliant Code Example 11 | 12 | ```javascript 13 | var person = {}; // Noncompliant 14 | person.firstName = "John"; 15 | person.middleInitial = "Q"; 16 | person.lastName = "Public"; 17 | ``` 18 | 19 | ## Compliant Solution 20 | 21 | ```javascript 22 | var person = { 23 | firstName: "John", 24 | middleInitial: "Q", 25 | lastName: "Public", 26 | }; 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/rules/prefer-single-boolean-return.md: -------------------------------------------------------------------------------- 1 | # prefer-single-boolean-return 2 | 3 | Return of boolean literal statements wrapped into `if-then-else` ones should be simplified. 4 | 5 | ## Noncompliant Code Example 6 | 7 | ```javascript 8 | if (expression) { 9 | return true; 10 | } else { 11 | return false; 12 | } 13 | ``` 14 | 15 | ## Compliant Solution 16 | 17 | ```javascript 18 | return expression; 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/rules/prefer-while.md: -------------------------------------------------------------------------------- 1 | # prefer-while 2 | 3 | :wrench: _fixable_ 4 | 5 | When only the condition expression is defined in a `for` loop, and the initialization and increment expressions are missing, a `while` loop should be used instead to increase readability. 6 | 7 | ## Noncompliant Code Example 8 | 9 | ```javascript 10 | for (; condition; ) { 11 | /*...*/ 12 | } 13 | ``` 14 | 15 | ## Compliant Solution 16 | 17 | ```javascript 18 | while (condition) { 19 | /*...*/ 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-radar", 3 | "version": "0.2.1", 4 | "author": "Brett Zamir", 5 | "contributors": [], 6 | "description": "Radar rules for ESLint", 7 | "main": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "repository": "https://github.com/es-joy/eslint-plugin-radar", 10 | "license": "LGPL-3.0-only", 11 | "keywords": [ 12 | "radar", 13 | "sonarjs", 14 | "eslint", 15 | "eslintplugin" 16 | ], 17 | "bugs": { 18 | "url": "https://github.com/es-joy/eslint-plugin-radar/issues" 19 | }, 20 | "homepage": "https://github.com/es-joy/eslint-plugin-radar", 21 | "engines": { 22 | "node": ">=10" 23 | }, 24 | "peerDependencies": { 25 | "eslint": ">= 3.0.0 <= 7.x.x" 26 | }, 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "@types/eslint": "7.2.6", 30 | "@types/estree": "0.0.46", 31 | "@types/jest": "26.0.20", 32 | "@types/lodash": "4.14.168", 33 | "@types/minimist": "1.2.1", 34 | "@types/node": "14.14.22", 35 | "@typescript-eslint/experimental-utils": "4.14.1", 36 | "@typescript-eslint/parser": "4.14.1", 37 | "babel-eslint": "10.1.0", 38 | "eslint": "7.18.0", 39 | "eslint-config-prettier": "7.2.0", 40 | "eslint-plugin-import": "2.22.1", 41 | "eslint-plugin-notice": "0.9.10", 42 | "eslint-plugin-radar": "^0.2.0", 43 | "jest": "26.6.3", 44 | "lint-staged": "10.5.3", 45 | "lodash": "4.17.20", 46 | "minimist": "1.2.5", 47 | "prettier": "2.2.1", 48 | "rimraf": "3.0.2", 49 | "ts-jest": "26.4.4", 50 | "ts-node": "9.1.1", 51 | "typescript": "4.1.3" 52 | }, 53 | "prettier": { 54 | "printWidth": 120, 55 | "trailingComma": "all" 56 | }, 57 | "jest": { 58 | "roots": [ 59 | "tests", 60 | "src" 61 | ], 62 | "collectCoverageFrom": [ 63 | "src/**/*.ts" 64 | ], 65 | "globals": { 66 | "ts-jest": { 67 | "babelConfig": false 68 | } 69 | }, 70 | "moduleFileExtensions": [ 71 | "ts", 72 | "js", 73 | "json" 74 | ], 75 | "transform": { 76 | "^.+\\.ts$": "ts-jest" 77 | }, 78 | "testMatch": [ 79 | "/tests/**/*.test.ts" 80 | ] 81 | }, 82 | "lint-staged": { 83 | "*.{ts,tsx,js}": [ 84 | "eslint", 85 | "prettier --write" 86 | ], 87 | "*.{json,md}": [ 88 | "prettier --write" 89 | ] 90 | }, 91 | "scripts": { 92 | "prettier": "prettier --list-different \"{src,tests}/**/*.{js,ts}\"", 93 | "eslint": "eslint --ext js,ts .", 94 | "lint": "yarn eslint && yarn prettier", 95 | "typecheck": "tsc -p tsconfig.json", 96 | "precommit": "lint-staged && yarn typecheck", 97 | "build": "rimraf lib && tsc -d -p tsconfig-src.json", 98 | "prepack": "yarn build", 99 | "ruling": "ts-node ruling/index.ts", 100 | "jest": "jest", 101 | "test": "jest --coverage" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /radar-project.properties: -------------------------------------------------------------------------------- 1 | radar.projectKey=eslint-plugin-radar 2 | radar.projectName=eslint-plugin-radar 3 | radar.projectDescription=Radar rules for ESLint 4 | 5 | # current version in development 6 | # should be bigger than the one in package.json 7 | radar.projectVersion=0.6.0 8 | 9 | radar.links.homepage=https://github.com/es-joy/eslint-plugin-radar 10 | radar.links.ci=https://travis-ci.com/es-joy/eslint-plugin-radar 11 | radar.links.issue=https://github.com/es-joy/eslint-plugin-radar/issues 12 | 13 | radar.sources=src 14 | radar.tests=tests 15 | 16 | radar.javascript.lcov.reportPaths=coverage/lcov.info 17 | radar.testExecutionReportPaths=test-report.xml 18 | -------------------------------------------------------------------------------- /ruling/.eslintignore: -------------------------------------------------------------------------------- 1 | tests/resources 2 | !**/node_modules/** 3 | -------------------------------------------------------------------------------- /ruling/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | "use strict"; 21 | module.exports = { 22 | rules: { 23 | "no-console": "off", 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /ruling/snapshots/max-switch-cases: -------------------------------------------------------------------------------- 1 | src/create-react-app/packages/react-scripts/fixtures/kitchensink/src/App.js: 53 2 | src/three.js/src/loaders/Loader.js: 175 3 | -------------------------------------------------------------------------------- /ruling/snapshots/no-all-duplicated-branches: -------------------------------------------------------------------------------- 1 | src/react-router/packages/react-router-native/experimental/StackRoute.js: 328 2 | src/react-router/website/modules/components/Home/Header.js: 99 3 | src/spectrum/api/models/message.js: 123 4 | src/spectrum/src/components/linkPreview/style.js: 6 5 | src/spectrum/src/views/notifications/components/sortAndGroupNotificationMessages.js: 69 6 | src/vue/packages/vue-server-renderer/basic.js: 1898 7 | src/vue/packages/vue-server-renderer/build.js: 1904 8 | src/vue/packages/vue-template-compiler/browser.js: 821,1688 9 | src/vue/packages/vue-template-compiler/build.js: 778,1645 10 | src/vue/packages/weex-template-compiler/build.js: 942,2936 11 | -------------------------------------------------------------------------------- /ruling/snapshots/no-duplicated-branches: -------------------------------------------------------------------------------- 1 | src/Chart.js/src/scales/scale.linear.js: 116,122 2 | src/Chart.js/src/scales/scale.logarithmic.js: 155,161 3 | src/Ghost/core/server/models/post.js: 198 4 | src/angular.js/src/ng/parse.js: 760,1327 5 | src/angular.js/src/ngAnimate/animateQueue.js: 194 6 | src/brackets/src/document/TextRange.js: 133 7 | src/brackets/src/extensions/default/JavaScriptQuickEdit/unittests.js: 151 8 | src/brackets/src/extensions/default/MDNDocs/InlineDocsViewer.js: 103 9 | src/brackets/src/language/CSSUtils.js: 1236 10 | src/brackets/src/utils/DragAndDrop.js: 54 11 | src/freeCodeCamp/server/utils/user-stats.js: 30 12 | src/jquery/external/sinon/sinon.js: 506 13 | src/react-native/Libraries/Renderer/ReactFabric-dev.js: 10298,10386 14 | src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 10668,10756 15 | src/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js: 4202,4310 16 | src/react-native/local-cli/util/Config.js: 36 17 | src/react/packages/react-reconciler/src/ReactFiberCommitWork.js: 669,758 18 | src/reveal.js/js/reveal.js: 1133 19 | src/spectrum/src/reducers/dashboardFeed.js: 46 20 | src/spectrum/src/views/notifications/components/sortAndGroupNotificationMessages.js: 72 21 | src/three.js/editor/js/History.js: 61 22 | src/three.js/src/renderers/WebGLRenderer.js: 1636,1640,1646 23 | src/vue/packages/vue-server-renderer/basic.js: 4378 24 | src/vue/packages/vue-server-renderer/build.js: 4121 25 | src/vue/packages/vue-template-compiler/browser.js: 3493 26 | src/vue/packages/vue-template-compiler/build.js: 3095 27 | src/vue/src/platforms/web/compiler/directives/model.js: 48 28 | -------------------------------------------------------------------------------- /ruling/snapshots/no-element-overwrite: -------------------------------------------------------------------------------- 1 | src/angular.js/src/auto/injector.js: 887 2 | -------------------------------------------------------------------------------- /ruling/snapshots/no-extra-arguments: -------------------------------------------------------------------------------- 1 | src/Chart.js/src/platforms/platform.dom.js: 420 2 | src/Ghost/core/server/api/mail.js: 83,140 3 | src/angular.js/src/ngAnimate/animateCssDriver.js: 182,183 4 | src/angular.js/src/ngAnimate/animateJs.js: 131 5 | src/angular.js/src/ngMock/angular-mocks.js: 2412 6 | src/brackets/src/extensions/default/StaticServer/node/node_modules/connect/node_modules/multiparty/node_modules/readable-stream/lib/_stream_readable.js: 706 7 | src/jquery/external/sinon/sinon.js: 1669 8 | src/react-native/Libraries/Renderer/ReactFabric-prod.js: 62,70,1470,1472,5192 9 | src/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js: 127,135,1686,1688,5590 10 | src/react-native/Libraries/polyfills/error-guard.js: 42 11 | src/react-native/local-cli/logAndroid/logAndroid.js: 17 12 | src/react-native/local-cli/logIOS/logIOS.js: 19 13 | src/react/scripts/bench/benchmarks/pe-no-components/benchmark.js: 1069,4701,4702 14 | src/react/scripts/bench/build.js: 74 15 | src/redux/src/applyMiddleware.js: 32 16 | src/spectrum/src/views/notifications/utils.js: 196 17 | src/three.js/src/geometries/ExtrudeGeometry.js: 75 18 | -------------------------------------------------------------------------------- /ruling/snapshots/no-identical-conditions: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ruling/snapshots/no-identical-expressions: -------------------------------------------------------------------------------- 1 | src/Ghost/core/server/models/relations/authors.js: 171 2 | src/angular.js/src/ng/directive/input.js: 1530 3 | src/angular.js/src/ng/directive/ngModel.js: 1091 4 | src/spectrum/api/authentication.js: 359 5 | src/spectrum/mobile/components/Messages/index.js: 22 6 | src/spectrum/src/components/badges/index.js: 72 7 | src/spectrum/src/components/listItems/index.js: 81 8 | src/spectrum/src/views/communityMembers/components/editDropdown.js: 245 9 | src/spectrum/src/views/directMessages/index.js: 169 10 | src/spectrum/src/views/login/index.js: 37 11 | src/vue/packages/vue-server-renderer/basic.js: 2795,2798 12 | src/vue/packages/vue-template-compiler/browser.js: 2218,2221 13 | -------------------------------------------------------------------------------- /ruling/snapshots/no-inverted-boolean-check: -------------------------------------------------------------------------------- 1 | src/angular.js/src/ng/sniffer.js: 61 2 | src/jest/packages/expect/src/matchers.js: 523 3 | src/react-native/Libraries/Renderer/ReactFabric-dev.js: 1447,5567,5796,8656,8743 4 | src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 1602,5903,6132,8992,9079 5 | src/spectrum/public/push-sw.js: 37 6 | src/three.js/src/math/Interpolant.js: 57,95 7 | -------------------------------------------------------------------------------- /ruling/snapshots/no-one-iteration-loop: -------------------------------------------------------------------------------- 1 | src/react-native/Libraries/Renderer/ReactFabric-dev.js: 12235 2 | src/react-native/Libraries/Renderer/ReactFabric-prod.js: 4675 3 | src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 12605 4 | src/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js: 5073 5 | src/react/packages/react-reconciler/src/ReactFiberScheduler.js: 948 6 | src/spectrum/src/views/directMessages/components/avatars.js: 93 7 | -------------------------------------------------------------------------------- /ruling/snapshots/no-redundant-boolean: -------------------------------------------------------------------------------- 1 | src/jest/packages/jest-cli/src/search_source.js: 118 2 | src/react-native/Libraries/Renderer/ReactFabric-dev.js: 4094,11584,11950,12022,12069,12104,12123,12159,12176,12246 3 | src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 4473,11954,12320,12392,12439,12474,12493,12529,12546,12616 4 | src/reveal.js/plugin/markdown/markdown.js: 333 5 | src/vue/packages/vue-server-renderer/build.js: 1681 6 | src/vue/packages/weex-template-compiler/build.js: 1990,2030 7 | src/vue/packages/weex-vue-framework/factory.js: 1553,2035,4456,4559 8 | -------------------------------------------------------------------------------- /ruling/snapshots/no-redundant-jump: -------------------------------------------------------------------------------- 1 | src/angular.js/src/ngTouch/swipe.js: 167 2 | src/brackets/src/extensibility/node/package-validator.js: 275 3 | src/brackets/src/extensions/default/CodeFolding/unittests.js: 167 4 | src/brackets/src/LiveDevelopment/MultiBrowserImpl/documents/LiveHTMLDocument.js: 241,252,263,274,285 5 | src/brackets/src/LiveDevelopment/MultiBrowserImpl/protocol/remote/LiveDevProtocolRemote.js: 88 6 | src/brackets/src/preferences/PreferencesBase.js: 618,638,733,754 7 | src/brackets/src/search/node/FindInFilesDomain.js: 283 8 | src/brackets/src/utils/DragAndDrop.js: 84 9 | src/create-react-app/packages/react-dev-utils/openBrowser.js: 58 10 | src/express/lib/router/index.js: 254 11 | src/react-native/bots/code-analysis-bot.js: 170 12 | src/react-native/Libraries/Lists/VirtualizedList.js: 1363 13 | src/react-native/local-cli/link/ios/removeFromPbxItemContainerProxySection.js: 22 14 | src/react-native/local-cli/link/ios/removeFromPbxReferenceProxySection.js: 21 15 | src/react-native/local-cli/link/ios/removeProductGroup.js: 17 16 | src/spectrum/athena/models/slackImports.js: 22 17 | src/spectrum/athena/queues/new-message-in-thread/group-replies.js: 41,44 18 | src/spectrum/pluto/changefeeds/openSourceStatus.js: 43 19 | src/spectrum/pluto/changefeeds/privateChannel.js: 27,40,79,101 20 | src/spectrum/pluto/queues/processAnalyticsRemoved.js: 63 21 | src/spectrum/pluto/queues/processModeratorRemoved.js: 92 22 | src/spectrum/pluto/queues/processOssStatusActivated.js: 155 23 | src/spectrum/pluto/queues/processOssStatusDisabled.js: 165 24 | src/spectrum/pluto/queues/processOssStatusEnabled.js: 155 25 | src/spectrum/pluto/queues/processPrioritySupportRemoved.js: 64 26 | src/spectrum/pluto/queues/processPrivateChannelRemoved.js: 92 27 | src/spectrum/pluto/webhooks/index.js: 65 28 | src/spectrum/src/components/composer/index.js: 485 29 | src/spectrum/src/components/editForm/user.js: 301 30 | src/spectrum/src/components/modals/ChangeChannelModal/index.js: 63 31 | src/spectrum/src/components/modals/CreateChannelModal/index.js: 247 32 | src/spectrum/src/components/modals/DeleteDoubleCheckModal/index.js: 95,123,152,181 33 | src/spectrum/src/components/modals/UpgradeModal/index.js: 80 34 | src/spectrum/src/components/profile/channel.js: 85 35 | src/spectrum/src/components/threadComposer/components/composer.js: 571 36 | src/spectrum/src/components/toggleChannelMembership/index.js: 85 37 | src/spectrum/src/components/toggleChannelNotifications/index.js: 62 38 | src/spectrum/src/components/upsell/joinChannel.js: 82 39 | src/spectrum/src/components/upsell/requestToJoinChannel.js: 73 40 | src/spectrum/src/helpers/directMessageThreads.js: 21 41 | src/spectrum/src/views/channel/components/notificationsToggle.js: 57 42 | src/spectrum/src/views/channelSettings/components/editForm.js: 130 43 | src/spectrum/src/views/channelSettings/index.js: 62,86 44 | src/spectrum/src/views/communityMembers/components/importSlack.js: 85 45 | src/spectrum/src/views/communitySettings/components/editForm.js: 236 46 | src/spectrum/src/views/directMessages/containers/newThread.js: 194,376,385,680 47 | src/spectrum/src/views/newCommunity/components/createCommunityForm/index.js: 401 48 | src/spectrum/src/views/newCommunity/components/editCommunityForm/index.js: 214 49 | src/spectrum/src/views/notifications/index.js: 106 50 | src/spectrum/vulcan/models/community.js: 21,36,55 51 | src/spectrum/vulcan/models/message.js: 25,40 52 | src/spectrum/vulcan/models/thread.js: 29,44,92,111 53 | src/spectrum/vulcan/models/user.js: 26,41,58,75 54 | -------------------------------------------------------------------------------- /ruling/snapshots/no-same-line-conditional: -------------------------------------------------------------------------------- 1 | src/brackets/src/extensions/default/JavaScriptCodeHints/thirdparty/requirejs/require.js: 11,12,20 2 | src/three.js/src/renderers/webgl/WebGLRenderLists.js: 37 3 | src/vue/packages/vue-server-renderer/basic.js: 5095 4 | src/vue/packages/vue-server-renderer/build.js: 4838 5 | src/vue/packages/vue-template-compiler/browser.js: 4337 6 | src/vue/packages/vue-template-compiler/build.js: 3939 7 | src/vue/packages/weex-template-compiler/build.js: 3356 8 | src/vue/src/compiler/codegen/index.js: 438 9 | -------------------------------------------------------------------------------- /ruling/snapshots/no-small-switch: -------------------------------------------------------------------------------- 1 | src/Ghost/core/server/lib/ghost-version.js: 11 2 | src/brackets/src/filesystem/impls/appshell/AppshellFileSystem.js: 429 3 | src/jquery/external/sinon/sinon.js: 5221 4 | src/react-native/Libraries/Renderer/ReactFabric-dev.js: 10010,13297 5 | src/react-native/Libraries/Renderer/ReactFabric-prod.js: 4043,5462 6 | src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 10380,13667 7 | src/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js: 4538,5860 8 | src/react-native/react-native-cli/index.js: 148 9 | src/react/fixtures/dom/src/components/fixtures/text-inputs/InputTestCase.js: 22 10 | src/react/packages/react-reconciler/src/ReactFiberCommitWork.js: 381 11 | src/react/packages/react-reconciler/src/ReactFiberReconciler.js: 501 12 | src/react/scripts/rollup/forks.js: 96 13 | src/react/scripts/rollup/packaging.js: 46 14 | src/reveal.js/plugin/search/search.js: 186 15 | src/spectrum/api/queries/community/contextPermissions.js: 12 16 | src/spectrum/api/routes/api/stripe.js: 62 17 | src/spectrum/athena/utils/push-notifications/send-expo-push-notifications.js: 64 18 | src/spectrum/pluto/queues/processStripeChargeWebhook.js: 20 19 | src/spectrum/pluto/queues/processStripeInvoiceWebhook.js: 50 20 | src/spectrum/pluto/queues/processStripeSourceWebhook.js: 20 21 | src/spectrum/src/components/editForm/index.js: 8 22 | src/spectrum/src/reducers/newUserOnboarding.js: 6 23 | src/spectrum/src/reducers/notifications.js: 7 24 | src/spectrum/src/views/channelSettings/index.js: 138 25 | -------------------------------------------------------------------------------- /ruling/snapshots/no-unused-collection: -------------------------------------------------------------------------------- 1 | src/angular.js/benchmarks/bootstrap-compile-bp/app.js: 43 2 | src/brackets/src/extensions/default/DebugCommands/NodeDebugUtils.js: 42 3 | src/brackets/src/project/FileTreeViewModel.js: 232 4 | src/brackets/src/utils/Async.js: 87 5 | src/Chart.js/src/core/core.controller.js: 319 6 | src/react-native/Libraries/Renderer/ReactFabric-dev.js: 3724,3725 7 | src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 4103,4104 8 | src/react/packages/react-reconciler/src/ReactFiber.js: 55,56 9 | src/spectrum/api/migrations/seed/index.js: 116,128 10 | src/vue/packages/vue-server-renderer/basic.js: 6710 11 | src/vue/packages/vue-server-renderer/build.js: 6455 12 | -------------------------------------------------------------------------------- /ruling/snapshots/no-use-of-empty-return-value: -------------------------------------------------------------------------------- 1 | src/brackets/src/extensions/default/InlineColorEditor/unittests.js: 410 2 | src/brackets/src/extensions/default/JavaScriptCodeHints/thirdparty/requirejs/require.js: 15,16,16,21,29 3 | src/freeCodeCamp/common/app/Map/redux/utils.js: 8 4 | src/react-native/Libraries/Renderer/ReactFabric-prod.js: 723,744,819,1621,3238,3572,4858,4942,4953,5299 5 | src/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js: 819,840,915,1809,3503,5256,5340,5351,5697 6 | src/react-native/local-cli/link/ios/unlinkAssets.js: 53 7 | -------------------------------------------------------------------------------- /ruling/snapshots/no-useless-catch: -------------------------------------------------------------------------------- 1 | src/create-react-app/tasks/screencast.js: 54 2 | -------------------------------------------------------------------------------- /ruling/snapshots/prefer-immediate-return: -------------------------------------------------------------------------------- 1 | src/Ghost/core/server/adapters/storage/LocalFileStorage.js: 48 2 | src/Ghost/core/server/api/mail.js: 120 3 | src/Ghost/core/server/lib/mobiledoc/cards/html.js: 19 4 | src/Ghost/core/server/models/relations/authors.js: 40 5 | src/angular.js/lib/versions/version-info.js: 40,118 6 | src/brackets/src/JSUtils/Session.js: 550 7 | src/brackets/src/editor/Editor.js: 1558 8 | src/brackets/src/extensions/default/CodeFolding/unittests.js: 146 9 | src/brackets/src/project/ProjectManager.js: 391 10 | src/brackets/src/utils/ExtensionLoader.js: 177 11 | src/brackets/src/utils/HealthLogger.js: 60 12 | src/create-react-app/packages/react-error-overlay/src/utils/parser.js: 30 13 | src/freeCodeCamp/common/models/user.js: 150 14 | src/jest/packages/jest-jasmine2/src/jasmine/Env.js: 310 15 | src/jest/packages/jest-jasmine2/src/jasmine/jasmine_light.js: 51,70 16 | src/jest/packages/jest-resolve/src/index.js: 378 17 | src/react-native/Libraries/Animated/src/nodes/AnimatedInterpolation.js: 359 18 | src/react-native/Libraries/Components/ScrollResponder.js: 385 19 | src/react-native/Libraries/Renderer/ReactFabric-dev.js: 3139,10554,10585,12291,13532 20 | src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 3589,10924,10955,12661 21 | src/react-native/RNTester/js/ScrollViewSimpleExample.js: 63 22 | src/react-native/jest/mockComponent.js: 13 23 | src/react-native/local-cli/bundle/assetPathUtils.js: 52 24 | src/react-native/local-cli/util/PackageManager.js: 40 25 | src/react/fixtures/attribute-behavior/src/App.js: 711 26 | src/react/packages/react-dom/src/client/ReactDOMFiberComponent.js: 1096 27 | src/react/packages/react-dom/src/client/ReactDOMFiberInput.js: 63 28 | src/react/packages/react-dom/src/client/ReactDOMFiberTextarea.js: 55 29 | src/react/packages/react-dom/src/client/inputValueTracking.js: 90 30 | src/react/packages/react-dom/src/events/ChangeEventPlugin.js: 281 31 | src/react/packages/react-dom/src/server/ReactDOMStringRenderer.js: 17,28 32 | src/react/packages/react-native-renderer/src/ReactFabricRenderer.js: 233 33 | src/react/packages/react-reconciler/src/ReactFiberHostContext.js: 55,86 34 | src/react/packages/react-reconciler/src/ReactFiberScheduler.js: 1004 35 | src/react/packages/react/src/ReactElement.js: 276 36 | src/react/packages/simple-cache-provider/src/SimpleCacheProvider.js: 196 37 | src/spectrum/api/migrations/seed/generate.js: 136 38 | src/spectrum/api/models/channel.js: 63 39 | src/spectrum/api/models/reputationEvents.js: 18 40 | src/spectrum/api/models/thread.js: 446 41 | src/spectrum/api/routes/api/graphql.js: 27 42 | src/spectrum/chronos/queues/digests/processReputation.js: 17 43 | src/spectrum/chronos/queues/digests/processThreads.js: 70 44 | src/spectrum/shared/get-mentions.js: 16 45 | src/spectrum/shared/graphql/queries/directMessageThread/getCurrentUserDMThreadConnection.js: 61 46 | src/spectrum/shared/sort-by-date.js: 20 47 | src/spectrum/src/components/messageGroup/index.js: 127 48 | src/spectrum/src/helpers/directMessageThreads.js: 8 49 | src/spectrum/src/helpers/utils.js: 257,266 50 | src/spectrum/src/views/directMessages/index.js: 109 51 | src/spectrum/src/views/newUserOnboarding/components/communitySearch/index.js: 80 52 | src/spectrum/vulcan/models/text-parsing.js: 49 53 | src/three.js/src/animation/AnimationClip.js: 305 54 | src/three.js/src/extras/core/Curve.js: 229 55 | src/three.js/src/renderers/webgl/WebGLPrograms.js: 132 56 | src/vue/packages/vue-server-renderer/basic.js: 7559,8054 57 | src/vue/packages/vue-server-renderer/build.js: 7304 58 | src/vue/packages/weex-vue-framework/index.js: 168 59 | src/vue/src/platforms/weex/entry-framework.js: 162 60 | -------------------------------------------------------------------------------- /ruling/snapshots/prefer-object-literal: -------------------------------------------------------------------------------- 1 | src/Ghost/core/server/helpers/index.js: 1 2 | src/Ghost/core/server/helpers/navigation.js: 64 3 | src/Ghost/core/server/lib/image/image-size.js: 23 4 | src/Ghost/core/server/update-check.js: 87 5 | src/angular.js/i18n/src/converter.js: 38 6 | src/angular.js/src/ngAnimate/animateCss.js: 600 7 | src/brackets/src/LiveDevelopment/LiveDevServerManager.js: 97 8 | src/brackets/src/extensibility/ExtensionManager.js: 313 9 | src/brackets/src/extensions/default/HealthData/HealthDataManager.js: 53 10 | src/brackets/src/extensions/default/InlineColorEditor/ColorEditor.js: 557,570,582 11 | src/brackets/src/extensions/default/JavaScriptRefactoring/RefactoringUtils.js: 365 12 | src/jest/packages/pretty-format/perf/test.js: 182 13 | src/jquery/external/sinon/sinon.js: 4500 14 | src/reveal.js/plugin/search/search.js: 165 15 | src/spectrum/api/models/user.js: 407 16 | src/three.js/editor/js/Command.js: 32 17 | src/three.js/editor/js/History.js: 164 18 | src/three.js/src/core/Geometry.js: 1286,1338 19 | src/three.js/src/core/Object3D.js: 649 20 | src/vue/packages/vue-server-renderer/basic.js: 3943 21 | src/vue/packages/vue-server-renderer/build.js: 3686 22 | src/vue/packages/vue-template-compiler/browser.js: 3058 23 | src/vue/packages/vue-template-compiler/build.js: 2660 24 | src/vue/packages/weex-template-compiler/build.js: 1459 25 | src/vue/packages/weex-vue-framework/factory.js: 3570,3572,5203 26 | src/vue/src/compiler/parser/index.js: 373 27 | src/vue/src/core/global-api/index.js: 22 28 | src/vue/src/core/instance/state.js: 314,316 29 | -------------------------------------------------------------------------------- /ruling/snapshots/prefer-single-boolean-return: -------------------------------------------------------------------------------- 1 | src/Ghost/core/server/adapters/storage/utils.js: 39 2 | src/Ghost/core/server/api/notifications.js: 73 3 | src/brackets/src/extensions/default/DebugCommands/main.js: 345 4 | src/brackets/src/search/FindUtils.js: 431 5 | src/spectrum/src/components/message/index.js: 17 6 | -------------------------------------------------------------------------------- /ruling/snapshots/prefer-while: -------------------------------------------------------------------------------- 1 | src/brackets/src/extensions/default/QuickView/main.js: 302 2 | src/create-react-app/packages/react-error-overlay/src/utils/getSourceMap.js: 84 3 | src/react-native/Libraries/Renderer/ReactFabric-prod.js: 616,619,621,1249,1252,1347,1366,2197,3462,3513,3724,4090,4572,4678,4681,4782,4927,4944,5046,5065 4 | src/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js: 712,715,717,1465,1468,1563,1582,2453,3855,3931,4166,4188,4227,4323,4365,4970,5076,5079,5180,5325,5342,5444,5463 5 | src/three.js/src/renderers/webgl/WebGLUniforms.js: 493 6 | -------------------------------------------------------------------------------- /scripts/analyze.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # analyze only on one axis of node versions 4 | if [ "${TRAVIS_NODE_VERSION}" != "8" ]; then 5 | echo 'Analysis ignored (nodejs version is not 8)' 6 | exit 0 7 | fi 8 | 9 | set -euo pipefail 10 | 11 | radar-scanner 12 | -------------------------------------------------------------------------------- /scripts/file-header.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | -------------------------------------------------------------------------------- /scripts/generate-rules-radar-meta.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import * as fs from "fs"; 21 | 22 | const outputPath = process.argv[2]; 23 | 24 | const meta = []; 25 | 26 | const readmeContent = fs.readFileSync("README.md", "utf-8"); 27 | 28 | const lines = readmeContent.split(/\n/); 29 | let beforeBug: "before-bug"; 30 | let codeSmell: "code-smell"; 31 | let state: beforeBug | "bug" | "between" | codeSmell = beforeBug; 32 | for (const line of lines) { 33 | if (state === "before-bug" && line.startsWith("*")) { 34 | state = "bug"; 35 | } 36 | 37 | if (state === "bug") { 38 | if (line.startsWith("*")) { 39 | addRule(line, "BUG"); 40 | } else { 41 | state = "between"; 42 | } 43 | } 44 | 45 | if (state === "between" && line.startsWith("*")) { 46 | state = codeSmell; 47 | } 48 | 49 | if (state === codeSmell) { 50 | if (line.startsWith("*")) { 51 | addRule(line, "CODE_SMELL"); 52 | } else { 53 | break; 54 | } 55 | } 56 | } 57 | 58 | function addRule(line: string, type: string) { 59 | const name = line.substr(2).split("([")[0].trim(); 60 | const key = "radar/" + line.split("`")[1].trim(); 61 | 62 | meta.push({ 63 | key, 64 | name, 65 | type, 66 | description: `See description of ESLint rule radar/${key} at the eslint-plugin-radar website.`, 67 | }); 68 | } 69 | 70 | fs.writeFileSync(outputPath, JSON.stringify(meta)); 71 | -------------------------------------------------------------------------------- /scripts/test-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # run tests with coverage and reports only for nodejs 10 6 | # this is required for sonarcloud analysis 7 | if [ "${TRAVIS_NODE_VERSION}" = "10" ]; then 8 | echo 'Running tests with coverage and reporter' 9 | # install `jest-sonar-reporter` here, because otherwise `yarn install` fails 10 | yarn add jest-sonar-reporter@2.0.0 11 | yarn test --coverage --testResultsProcessor jest-sonar-reporter 12 | else 13 | echo 'Running tests' 14 | yarn test 15 | fi 16 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { Linter } from "eslint"; 21 | 22 | const radarRules: [string, Linter.RuleLevel][] = [ 23 | ["cognitive-complexity", "error"], 24 | ["max-switch-cases", "error"], 25 | ["no-all-duplicated-branches", "error"], 26 | ["no-collapsible-if", "error"], 27 | ["no-collection-size-mischeck", "error"], 28 | ["no-duplicate-string", "error"], 29 | ["no-duplicated-branches", "error"], 30 | ["no-element-overwrite", "error"], 31 | ["no-extra-arguments", "error"], 32 | ["no-identical-conditions", "error"], 33 | ["no-identical-functions", "error"], 34 | ["no-identical-expressions", "error"], 35 | ["no-inverted-boolean-check", "error"], 36 | ["no-one-iteration-loop", "error"], 37 | ["no-redundant-boolean", "error"], 38 | ["no-redundant-jump", "error"], 39 | ["no-same-line-conditional", "error"], 40 | ["no-small-switch", "error"], 41 | ["no-unused-collection", "error"], 42 | ["no-use-of-empty-return-value", "error"], 43 | ["no-useless-catch", "error"], 44 | ["prefer-immediate-return", "error"], 45 | ["prefer-object-literal", "error"], 46 | ["prefer-single-boolean-return", "error"], 47 | ["prefer-while", "error"], 48 | ]; 49 | 50 | const radarRuleModules: any = {}; 51 | 52 | const configs: { recommended: Linter.Config & { plugins: string[] } } = { 53 | recommended: { plugins: ["radar"], rules: {} }, 54 | }; 55 | 56 | radarRules.forEach((rule) => (radarRuleModules[rule[0]] = require(`./rules/${rule[0]}`))); 57 | radarRules.forEach((rule) => (configs.recommended.rules![`radar/${rule[0]}`] = rule[1])); 58 | 59 | export { radarRuleModules as rules, configs }; 60 | -------------------------------------------------------------------------------- /src/rules/max-switch-cases.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1479 21 | 22 | import { Rule } from "eslint"; 23 | import { Node, SwitchStatement, SwitchCase } from "estree"; 24 | 25 | const MESSAGE = "Reduce the number of non-empty switch cases from {{numSwitchCases}} to at most {{maxSwitchCases}}."; 26 | 27 | const DEFAULT_MAX_SWITCH_CASES = 30; 28 | let maxSwitchCases = DEFAULT_MAX_SWITCH_CASES; 29 | 30 | const rule: Rule.RuleModule = { 31 | meta: { 32 | type: "suggestion", 33 | docs: { 34 | description: '"switch" statements should not have too many "case" clauses', 35 | category: "Code Smell Detection", 36 | recommended: true, 37 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/max-switch-cases.md", 38 | }, 39 | schema: [ 40 | { 41 | type: "integer", 42 | minimum: 0, 43 | }, 44 | ], 45 | }, 46 | create(context: Rule.RuleContext) { 47 | if (context.options.length > 0) { 48 | maxSwitchCases = context.options[0]; 49 | } 50 | return { SwitchStatement: (node: Node) => visitSwitchStatement(node as SwitchStatement, context) }; 51 | }, 52 | }; 53 | 54 | function visitSwitchStatement(switchStatement: SwitchStatement, context: Rule.RuleContext) { 55 | const nonEmptyCases = switchStatement.cases.filter( 56 | (switchCase) => switchCase.consequent.length > 0 && !isDefaultCase(switchCase), 57 | ); 58 | if (nonEmptyCases.length > maxSwitchCases) { 59 | const switchKeyword = context.getSourceCode().getFirstToken(switchStatement)!; 60 | context.report({ 61 | message: MESSAGE, 62 | loc: switchKeyword.loc, 63 | data: { numSwitchCases: nonEmptyCases.length.toString(), maxSwitchCases: maxSwitchCases.toString() }, 64 | }); 65 | } 66 | } 67 | 68 | function isDefaultCase(switchCase: SwitchCase) { 69 | return switchCase.test === null; 70 | } 71 | 72 | export = rule; 73 | -------------------------------------------------------------------------------- /src/rules/no-all-duplicated-branches.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-3923 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { getParent, isIfStatement } from "../utils/nodes"; 25 | import { areEquivalent } from "../utils/equivalence"; 26 | import { collectIfBranches, collectSwitchBranches } from "../utils/conditions"; 27 | 28 | const MESSAGE = "Remove this conditional structure or edit its code blocks so that they're not all the same."; 29 | const MESSAGE_CONDITIONAL_EXPRESSION = 30 | 'This conditional operation returns the same value whether the condition is "true" or "false".'; 31 | 32 | const rule: Rule.RuleModule = { 33 | meta: { 34 | type: "problem", 35 | docs: { 36 | description: "All branches in a conditional structure should not have exactly the same implementation", 37 | category: "Bug Detection", 38 | recommended: true, 39 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-all-duplicated-branches.md", 40 | }, 41 | }, 42 | create(context: Rule.RuleContext) { 43 | return { 44 | IfStatement(node: estree.Node) { 45 | const ifStmt = node as estree.IfStatement; 46 | 47 | // don't visit `else if` statements 48 | const parent = getParent(context); 49 | if (!isIfStatement(parent)) { 50 | const { branches, endsWithElse } = collectIfBranches(ifStmt); 51 | if (endsWithElse && allDuplicated(branches)) { 52 | context.report({ message: MESSAGE, node: ifStmt }); 53 | } 54 | } 55 | }, 56 | 57 | SwitchStatement(node: estree.Node) { 58 | const switchStmt = node as estree.SwitchStatement; 59 | const { branches, endsWithDefault } = collectSwitchBranches(switchStmt); 60 | if (endsWithDefault && allDuplicated(branches)) { 61 | context.report({ message: MESSAGE, node: switchStmt }); 62 | } 63 | }, 64 | 65 | ConditionalExpression(node: estree.Node) { 66 | const conditional = node as estree.ConditionalExpression; 67 | const branches = [conditional.consequent, conditional.alternate]; 68 | if (allDuplicated(branches)) { 69 | context.report({ message: MESSAGE_CONDITIONAL_EXPRESSION, node: conditional }); 70 | } 71 | }, 72 | }; 73 | 74 | function allDuplicated(branches: Array) { 75 | return ( 76 | branches.length > 1 && 77 | branches.slice(1).every((branch, index) => { 78 | return areEquivalent(branch, branches[index], context.getSourceCode()); 79 | }) 80 | ); 81 | } 82 | }, 83 | }; 84 | 85 | export = rule; 86 | -------------------------------------------------------------------------------- /src/rules/no-collapsible-if.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1066 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { isIfStatement, isBlockStatement } from "../utils/nodes"; 25 | import { report, issueLocation } from "../utils/locations"; 26 | 27 | const rule: Rule.RuleModule = { 28 | meta: { 29 | type: "suggestion", 30 | docs: { 31 | description: 'Collapsible "if" statements should be merged', 32 | category: "Code Smell Detection", 33 | recommended: true, 34 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-collapsible-if.md", 35 | }, 36 | schema: [ 37 | { 38 | // internal parameter 39 | enum: ["radar-runtime"], 40 | }, 41 | ], 42 | }, 43 | create(context: Rule.RuleContext) { 44 | return { 45 | IfStatement(node: estree.Node) { 46 | let { consequent } = node as estree.IfStatement; 47 | if (isBlockStatement(consequent) && consequent.body.length === 1) { 48 | consequent = consequent.body[0]; 49 | } 50 | if (isIfStatementWithoutElse(node) && isIfStatementWithoutElse(consequent)) { 51 | const ifKeyword = context.getSourceCode().getFirstToken(consequent); 52 | const enclosingIfKeyword = context.getSourceCode().getFirstToken(node); 53 | if (ifKeyword && enclosingIfKeyword) { 54 | report( 55 | context, 56 | { 57 | message: `Merge this if statement with the nested one.`, 58 | loc: enclosingIfKeyword.loc, 59 | }, 60 | [issueLocation(ifKeyword.loc, ifKeyword.loc, `Nested "if" statement.`)], 61 | ); 62 | } 63 | } 64 | }, 65 | }; 66 | 67 | function isIfStatementWithoutElse(node: estree.Node): node is estree.IfStatement { 68 | return isIfStatement(node) && !node.alternate; 69 | } 70 | }, 71 | }; 72 | 73 | export = rule; 74 | -------------------------------------------------------------------------------- /src/rules/no-collection-size-mischeck.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-3981 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { TSESTree } from "@typescript-eslint/experimental-utils"; 25 | import { isRequiredParserServices, RequiredParserServices } from "../utils/parser-services"; 26 | 27 | const CollectionLike = ["Array", "Map", "Set", "WeakMap", "WeakSet"]; 28 | const CollectionSizeLike = ["length", "size"]; 29 | 30 | const rule: Rule.RuleModule = { 31 | meta: { 32 | type: "problem", 33 | docs: { 34 | description: "Collection sizes and array length comparisons should make sense", 35 | category: "Code Smell Detection", 36 | recommended: true, 37 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-collection-size-mischeck.md", 38 | }, 39 | }, 40 | create(context: Rule.RuleContext) { 41 | const services = context.parserServices; 42 | const isTypeCheckerAvailable = isRequiredParserServices(services); 43 | return { 44 | BinaryExpression: (node: estree.Node) => { 45 | const expr = node as estree.BinaryExpression; 46 | if (["<", ">="].includes(expr.operator)) { 47 | const lhs = expr.left; 48 | const rhs = expr.right; 49 | if (isZeroLiteral(rhs) && lhs.type === "MemberExpression") { 50 | const { object, property } = lhs; 51 | if ( 52 | property.type === "Identifier" && 53 | CollectionSizeLike.includes(property.name) && 54 | (!isTypeCheckerAvailable || isCollection(object, services)) 55 | ) { 56 | context.report({ 57 | message: `Fix this expression; ${property.name} of "${context 58 | .getSourceCode() 59 | .getText(object)}" is always greater or equal to zero.`, 60 | node, 61 | }); 62 | } 63 | } 64 | } 65 | }, 66 | }; 67 | }, 68 | }; 69 | 70 | function isZeroLiteral(node: estree.Node) { 71 | return node.type === "Literal" && node.value === 0; 72 | } 73 | 74 | function isCollection(node: estree.Node, services: RequiredParserServices) { 75 | const checker = services.program.getTypeChecker(); 76 | const tp = checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(node as TSESTree.Node)); 77 | return !!tp.symbol && CollectionLike.includes(tp.symbol.name); 78 | } 79 | 80 | export = rule; 81 | -------------------------------------------------------------------------------- /src/rules/no-duplicate-string.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1192 21 | 22 | import { Rule } from "eslint"; 23 | import { Node, SimpleLiteral } from "estree"; 24 | import { getParent } from "../utils/nodes"; 25 | 26 | // Number of times a literal must be duplicated to trigger an issue 27 | const DEFAULT_THRESHOLD = 3; 28 | const MIN_LENGTH = 10; 29 | const NO_SEPARATOR_REGEXP = /^\w*$/; 30 | const EXCLUDED_CONTEXTS = ["ImportDeclaration", "JSXAttribute", "ExportAllDeclaration", "ExportNamedDeclaration"]; 31 | const MESSAGE = "Define a constant instead of duplicating this literal {{times}} times."; 32 | 33 | const rule: Rule.RuleModule = { 34 | meta: { 35 | type: "suggestion", 36 | docs: { 37 | description: "String literals should not be duplicated", 38 | category: "Code Smell Detection", 39 | recommended: true, 40 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-duplicate-string.md", 41 | }, 42 | schema: [{ type: "integer", minimum: 2 }], 43 | }, 44 | 45 | create(context: Rule.RuleContext) { 46 | const literalsByValue: Map = new Map(); 47 | const threshold: number = context.options[0] !== undefined ? context.options[0] : DEFAULT_THRESHOLD; 48 | 49 | return { 50 | Literal: (node: Node) => { 51 | const literal = node as SimpleLiteral; 52 | const parent = getParent(context); 53 | if (typeof literal.value === "string" && parent && parent.type !== "ExpressionStatement") { 54 | const stringContent = literal.value.trim(); 55 | 56 | if ( 57 | !isExcludedByUsageContext(context, literal) && 58 | stringContent.length >= MIN_LENGTH && 59 | !stringContent.match(NO_SEPARATOR_REGEXP) 60 | ) { 61 | const sameStringLiterals = literalsByValue.get(stringContent) || []; 62 | sameStringLiterals.push(literal); 63 | literalsByValue.set(stringContent, sameStringLiterals); 64 | } 65 | } 66 | }, 67 | 68 | "Program:exit"() { 69 | literalsByValue.forEach((literals) => { 70 | if (literals.length >= threshold) { 71 | context.report({ 72 | message: MESSAGE, 73 | node: literals[0], 74 | data: { times: literals.length.toString() }, 75 | }); 76 | } 77 | }); 78 | }, 79 | }; 80 | }, 81 | }; 82 | 83 | function isExcludedByUsageContext(context: Rule.RuleContext, literal: SimpleLiteral) { 84 | const parent = getParent(context)!; 85 | const parentType = parent.type; 86 | 87 | return ( 88 | EXCLUDED_CONTEXTS.includes(parentType) || isRequireContext(parent, context) || isObjectPropertyKey(parent, literal) 89 | ); 90 | } 91 | 92 | function isRequireContext(parent: Node, context: Rule.RuleContext) { 93 | return parent.type === "CallExpression" && context.getSourceCode().getText(parent.callee) === "require"; 94 | } 95 | 96 | function isObjectPropertyKey(parent: Node, literal: SimpleLiteral) { 97 | return parent.type === "Property" && parent.key === literal; 98 | } 99 | 100 | export = rule; 101 | -------------------------------------------------------------------------------- /src/rules/no-identical-conditions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1862 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { isIfStatement } from "../utils/nodes"; 25 | import { areEquivalent } from "../utils/equivalence"; 26 | import { report, issueLocation } from "../utils/locations"; 27 | 28 | const rule: Rule.RuleModule = { 29 | meta: { 30 | type: "problem", 31 | docs: { 32 | description: 'Related "if/else if" statements should not have the same condition', 33 | category: "Bug Detection", 34 | recommended: true, 35 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-identical-conditions.md", 36 | }, 37 | schema: [ 38 | { 39 | // internal parameter 40 | enum: ["radar-runtime"], 41 | }, 42 | ], 43 | }, 44 | create(context: Rule.RuleContext) { 45 | return { 46 | IfStatement(node: estree.Node) { 47 | const ifStmt = node as estree.IfStatement; 48 | const condition = ifStmt.test; 49 | let statement = ifStmt.alternate; 50 | while (statement) { 51 | if (isIfStatement(statement)) { 52 | if (areEquivalent(condition, statement.test, context.getSourceCode())) { 53 | const line = ifStmt.loc && ifStmt.loc.start.line; 54 | if (line && condition.loc) { 55 | report( 56 | context, 57 | { 58 | message: `This branch duplicates the one on line ${line}`, 59 | node: statement.test, 60 | }, 61 | [issueLocation(condition.loc, condition.loc, "Original")], 62 | ); 63 | } 64 | } 65 | statement = statement.alternate; 66 | } else { 67 | break; 68 | } 69 | } 70 | }, 71 | }; 72 | }, 73 | }; 74 | 75 | export = rule; 76 | -------------------------------------------------------------------------------- /src/rules/no-identical-expressions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1764 21 | 22 | import { Rule } from "eslint"; 23 | import { Node, BinaryExpression, LogicalExpression } from "estree"; 24 | import { isIdentifier, isLiteral } from "../utils/nodes"; 25 | import { areEquivalent } from "../utils/equivalence"; 26 | import { report, issueLocation, IssueLocation } from "../utils/locations"; 27 | 28 | const EQUALITY_OPERATOR_TOKEN_KINDS = new Set(["==", "===", "!=", "!=="]); 29 | 30 | // consider only binary expressions with these operators 31 | const RELEVANT_OPERATOR_TOKEN_KINDS = new Set(["&&", "||", "/", "-", "<<", ">>", "<", "<=", ">", ">="]); 32 | 33 | const message = (operator: string) => 34 | `Correct one of the identical sub-expressions on both sides of operator "${operator}"`; 35 | 36 | function hasRelevantOperator(node: BinaryExpression | LogicalExpression) { 37 | return ( 38 | RELEVANT_OPERATOR_TOKEN_KINDS.has(node.operator) || 39 | (EQUALITY_OPERATOR_TOKEN_KINDS.has(node.operator) && !hasIdentifierOperands(node)) 40 | ); 41 | } 42 | 43 | function hasIdentifierOperands(node: BinaryExpression | LogicalExpression) { 44 | return isIdentifier(node.left) && isIdentifier(node.right); 45 | } 46 | 47 | function isOneOntoOneShifting(node: BinaryExpression | LogicalExpression) { 48 | return node.operator === "<<" && isLiteral(node.left) && node.left.value === 1; 49 | } 50 | 51 | const rule: Rule.RuleModule = { 52 | meta: { 53 | type: "problem", 54 | docs: { 55 | description: "Identical expressions should not be used on both sides of a binary operator", 56 | category: "Bug Detection", 57 | recommended: true, 58 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-identical-expressions.md", 59 | }, 60 | schema: [ 61 | { 62 | // internal parameter 63 | enum: ["radar-runtime"], 64 | }, 65 | ], 66 | }, 67 | create(context: Rule.RuleContext) { 68 | return { 69 | LogicalExpression(node: Node) { 70 | check(node as LogicalExpression); 71 | }, 72 | BinaryExpression(node: Node) { 73 | check(node as BinaryExpression); 74 | }, 75 | }; 76 | 77 | function check(expr: BinaryExpression | LogicalExpression) { 78 | if ( 79 | hasRelevantOperator(expr) && 80 | !isOneOntoOneShifting(expr) && 81 | areEquivalent(expr.left, expr.right, context.getSourceCode()) 82 | ) { 83 | const secondaryLocations: IssueLocation[] = []; 84 | if (expr.left.loc) { 85 | secondaryLocations.push(issueLocation(expr.left.loc)); 86 | } 87 | report( 88 | context, 89 | { 90 | message: message(expr.operator), 91 | node: isRadarRuntime() ? expr.right : expr, 92 | }, 93 | secondaryLocations, 94 | ); 95 | } 96 | } 97 | 98 | function isRadarRuntime() { 99 | return context.options[context.options.length - 1] === "radar-runtime"; 100 | } 101 | }, 102 | }; 103 | 104 | export = rule; 105 | -------------------------------------------------------------------------------- /src/rules/no-identical-functions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-4144 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { areEquivalent } from "../utils/equivalence"; 25 | import { getMainFunctionTokenLocation, report, issueLocation } from "../utils/locations"; 26 | import { getParent } from "../utils/nodes"; 27 | 28 | const message = (line: string) => 29 | `Update this function so that its implementation is not identical to the one on line ${line}.`; 30 | 31 | const rule: Rule.RuleModule = { 32 | meta: { 33 | type: "problem", 34 | docs: { 35 | description: "Functions should not have identical implementations", 36 | category: "Code Smell Detection", 37 | recommended: true, 38 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-identical-functions.md", 39 | }, 40 | schema: [ 41 | { 42 | enum: ["radar-runtime"], 43 | }, 44 | ], 45 | }, 46 | create(context: Rule.RuleContext) { 47 | const functions: Array<{ function: estree.Function; parent: estree.Node | undefined }> = []; 48 | 49 | return { 50 | FunctionDeclaration(node: estree.Node) { 51 | visitFunction(node as estree.FunctionDeclaration); 52 | }, 53 | FunctionExpression(node: estree.Node) { 54 | visitFunction(node as estree.FunctionExpression); 55 | }, 56 | ArrowFunctionExpression(node: estree.Node) { 57 | visitFunction(node as estree.ArrowFunctionExpression); 58 | }, 59 | 60 | "Program:exit"() { 61 | processFunctions(); 62 | }, 63 | }; 64 | 65 | function visitFunction(node: estree.Function) { 66 | if (isBigEnough(node.body)) { 67 | functions.push({ function: node, parent: getParent(context) }); 68 | } 69 | } 70 | 71 | function processFunctions() { 72 | if (functions.length < 2) { 73 | return; 74 | } 75 | 76 | for (let i = 1; i < functions.length; i++) { 77 | const duplicatingFunction = functions[i].function; 78 | 79 | for (let j = 0; j < i; j++) { 80 | const originalFunction = functions[j].function; 81 | 82 | if ( 83 | areEquivalent(duplicatingFunction.body, originalFunction.body, context.getSourceCode()) && 84 | originalFunction.loc 85 | ) { 86 | const loc = getMainFunctionTokenLocation(duplicatingFunction, functions[i].parent, context); 87 | const originalFunctionLoc = getMainFunctionTokenLocation(originalFunction, functions[j].parent, context); 88 | const secondaryLocations = [ 89 | issueLocation(originalFunctionLoc, originalFunctionLoc, "Original implementation"), 90 | ]; 91 | report( 92 | context, 93 | { 94 | message: message(String(originalFunction.loc.start.line)), 95 | loc, 96 | }, 97 | secondaryLocations, 98 | ); 99 | break; 100 | } 101 | } 102 | } 103 | } 104 | 105 | function isBigEnough(node: estree.Expression | estree.Statement) { 106 | const tokens = context.getSourceCode().getTokens(node); 107 | 108 | if (tokens.length > 0 && tokens[0].value === "{") { 109 | tokens.shift(); 110 | } 111 | 112 | if (tokens.length > 0 && tokens[tokens.length - 1].value === "}") { 113 | tokens.pop(); 114 | } 115 | 116 | if (tokens.length > 0) { 117 | const firstLine = tokens[0].loc.start.line; 118 | const lastLine = tokens[tokens.length - 1].loc.end.line; 119 | 120 | return lastLine - firstLine > 1; 121 | } 122 | 123 | return false; 124 | } 125 | }, 126 | }; 127 | 128 | export = rule; 129 | -------------------------------------------------------------------------------- /src/rules/no-inverted-boolean-check.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1940 21 | 22 | import { Rule } from "eslint"; 23 | import { Node, UnaryExpression } from "estree"; 24 | import { isBinaryExpression } from "../utils/nodes"; 25 | 26 | const MESSAGE = "Use the opposite operator ({{invertedOperator}}) instead."; 27 | 28 | const invertedOperators: { [operator: string]: string } = { 29 | "==": "!=", 30 | "!=": "==", 31 | "===": "!==", 32 | "!==": "===", 33 | ">": "<=", 34 | "<": ">=", 35 | ">=": "<", 36 | "<=": ">", 37 | }; 38 | 39 | const rule: Rule.RuleModule = { 40 | meta: { 41 | type: "suggestion", 42 | docs: { 43 | description: "Boolean checks should not be inverted", 44 | category: "Code Smell Detection", 45 | recommended: true, 46 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-inverted-boolean-check.md", 47 | }, 48 | fixable: "code", 49 | }, 50 | create(context: Rule.RuleContext) { 51 | return { UnaryExpression: (node: Node) => visitUnaryExpression(node as UnaryExpression, context) }; 52 | }, 53 | }; 54 | 55 | function visitUnaryExpression(unaryExpression: UnaryExpression, context: Rule.RuleContext) { 56 | if (unaryExpression.operator === "!" && isBinaryExpression(unaryExpression.argument)) { 57 | const condition = unaryExpression.argument; 58 | const invertedOperator = invertedOperators[condition.operator]; 59 | if (invertedOperator) { 60 | const left = context.getSourceCode().getText(condition.left); 61 | const right = context.getSourceCode().getText(condition.right); 62 | context.report({ 63 | message: MESSAGE, 64 | data: { invertedOperator }, 65 | node: unaryExpression, 66 | fix: (fixer) => fixer.replaceText(unaryExpression, `${left} ${invertedOperator} ${right}`), 67 | }); 68 | } 69 | } 70 | } 71 | 72 | export = rule; 73 | -------------------------------------------------------------------------------- /src/rules/no-one-iteration-loop.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1751 21 | 22 | import { Rule } from "eslint"; 23 | import { Node, WhileStatement, ForStatement } from "estree"; 24 | import { isContinueStatement, getParent } from "../utils/nodes"; 25 | 26 | const rule: Rule.RuleModule = { 27 | meta: { 28 | type: "problem", 29 | docs: { 30 | description: "Loops with at most one iteration should be refactored", 31 | category: "Bug Detection", 32 | recommended: true, 33 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-one-iteration-loop.md", 34 | }, 35 | }, 36 | create(context: Rule.RuleContext) { 37 | const loopingNodes: Set = new Set(); 38 | const loops: Set = new Set(); 39 | const loopsAndTheirSegments: Array<{ loop: WhileStatement | ForStatement; segments: Rule.CodePathSegment[] }> = []; 40 | const currentCodePaths: Rule.CodePath[] = []; 41 | 42 | return { 43 | ForStatement(node: Node) { 44 | loops.add(node); 45 | }, 46 | WhileStatement(node: Node) { 47 | loops.add(node); 48 | }, 49 | DoWhileStatement(node: Node) { 50 | loops.add(node); 51 | }, 52 | 53 | onCodePathStart(codePath: Rule.CodePath) { 54 | currentCodePaths.push(codePath); 55 | }, 56 | 57 | onCodePathEnd() { 58 | currentCodePaths.pop(); 59 | }, 60 | 61 | "WhileStatement > *"() { 62 | const parent = getParent(context); 63 | visitLoopChild(parent as WhileStatement); 64 | }, 65 | 66 | "ForStatement > *"() { 67 | const parent = getParent(context); 68 | visitLoopChild(parent as ForStatement); 69 | }, 70 | 71 | onCodePathSegmentLoop(_, toSegment: Rule.CodePathSegment, node: Node) { 72 | if (isContinueStatement(node)) { 73 | loopsAndTheirSegments.forEach(({ segments, loop }) => { 74 | if (segments.includes(toSegment)) { 75 | loopingNodes.add(loop); 76 | } 77 | }); 78 | } else { 79 | loopingNodes.add(node); 80 | } 81 | }, 82 | 83 | "Program:exit"() { 84 | loops.forEach((loop) => { 85 | if (!loopingNodes.has(loop)) { 86 | context.report({ 87 | message: "Refactor this loop to do more than one iteration.", 88 | loc: context.getSourceCode().getFirstToken(loop)!.loc, 89 | }); 90 | } 91 | }); 92 | }, 93 | }; 94 | 95 | // Required to correctly process "continue" looping. 96 | // When a loop has a "continue" statement, this "continue" statement triggers a "onCodePathSegmentLoop" event, 97 | // and the corresponding event node is that "continue" statement. Current implementation is based on the fact 98 | // that the "onCodePathSegmentLoop" event is triggerent with a loop node. To work this special case around, 99 | // we visit loop children and collect corresponding path segments as these segments are "toSegment" 100 | // in "onCodePathSegmentLoop" event. 101 | function visitLoopChild(parent: WhileStatement | ForStatement) { 102 | if (currentCodePaths.length > 0) { 103 | const currentCodePath = currentCodePaths[currentCodePaths.length - 1]; 104 | loopsAndTheirSegments.push({ segments: currentCodePath.currentSegments, loop: parent }); 105 | } 106 | } 107 | }, 108 | }; 109 | 110 | export = rule; 111 | -------------------------------------------------------------------------------- /src/rules/no-redundant-boolean.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1125 21 | 22 | import { Rule } from "eslint"; 23 | import { BinaryExpression, Node, LogicalExpression, UnaryExpression, Expression } from "estree"; 24 | import { getParent, isBooleanLiteral, isIfStatement, isConditionalExpression } from "../utils/nodes"; 25 | 26 | const MESSAGE = "Remove the unnecessary boolean literal."; 27 | 28 | const rule: Rule.RuleModule = { 29 | meta: { 30 | type: "suggestion", 31 | docs: { 32 | description: "Boolean literals should not be redundant", 33 | category: "Code Smell Detection", 34 | recommended: true, 35 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-redundant-boolean.md", 36 | }, 37 | }, 38 | create(context: Rule.RuleContext) { 39 | return { 40 | BinaryExpression(node: Node) { 41 | const expression = node as BinaryExpression; 42 | if (expression.operator === "==" || expression.operator === "!=") { 43 | checkBooleanLiteral(expression.left); 44 | checkBooleanLiteral(expression.right); 45 | } 46 | }, 47 | 48 | LogicalExpression(node: Node) { 49 | const expression = node as LogicalExpression; 50 | checkBooleanLiteral(expression.left); 51 | 52 | if (expression.operator === "&&") { 53 | checkBooleanLiteral(expression.right); 54 | } 55 | 56 | // ignore `x || true` and `x || false` expressions outside of conditional expressions and `if` statements 57 | const parent = getParent(context); 58 | if ( 59 | expression.operator === "||" && 60 | ((isConditionalExpression(parent) && parent.test === expression) || isIfStatement(parent)) 61 | ) { 62 | checkBooleanLiteral(expression.right); 63 | } 64 | }, 65 | 66 | UnaryExpression(node: Node) { 67 | const unaryExpression = node as UnaryExpression; 68 | if (unaryExpression.operator === "!") { 69 | checkBooleanLiteral(unaryExpression.argument); 70 | } 71 | }, 72 | }; 73 | 74 | function checkBooleanLiteral(expression: Expression) { 75 | if (isBooleanLiteral(expression)) { 76 | context.report({ message: MESSAGE, node: expression }); 77 | } 78 | } 79 | }, 80 | }; 81 | 82 | export = rule; 83 | -------------------------------------------------------------------------------- /src/rules/no-redundant-jump.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-3626 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { getParent } from "../utils/nodes"; 25 | 26 | const message = "Remove this redundant jump."; 27 | const loops = "WhileStatement, ForStatement, DoWhileStatement, ForInStatement, ForOfStatement"; 28 | 29 | const rule: Rule.RuleModule = { 30 | meta: { 31 | type: "suggestion", 32 | docs: { 33 | description: "Jump statements should not be redundant", 34 | category: "Code Smell Detection", 35 | recommended: true, 36 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-redundant-jump.md", 37 | }, 38 | }, 39 | create(context: Rule.RuleContext) { 40 | function reportIfLastStatement(node: estree.ContinueStatement | estree.ReturnStatement) { 41 | const withArgument = node.type === "ContinueStatement" ? !!node.label : !!node.argument; 42 | if (!withArgument) { 43 | const block = getParent(context) as estree.BlockStatement; 44 | if (block.body[block.body.length - 1] === node && block.body.length > 1) { 45 | context.report({ 46 | message, 47 | node, 48 | }); 49 | } 50 | } 51 | } 52 | 53 | function reportIfLastStatementInsideIf(node: estree.ContinueStatement | estree.ReturnStatement) { 54 | const ancestors = context.getAncestors(); 55 | const ifStatement = ancestors[ancestors.length - 2]; 56 | const upperBlock = ancestors[ancestors.length - 3] as estree.BlockStatement; 57 | if (upperBlock.body[upperBlock.body.length - 1] === ifStatement) { 58 | reportIfLastStatement(node); 59 | } 60 | } 61 | 62 | return { 63 | [`:matches(${loops}) > BlockStatement > ContinueStatement`]: (node: estree.Node) => { 64 | reportIfLastStatement(node as estree.ContinueStatement); 65 | }, 66 | 67 | [`:matches(${loops}) > BlockStatement > IfStatement > BlockStatement > ContinueStatement`]: ( 68 | node: estree.Node, 69 | ) => { 70 | reportIfLastStatementInsideIf(node as estree.ContinueStatement); 71 | }, 72 | 73 | ":function > BlockStatement > ReturnStatement": (node: estree.Node) => { 74 | reportIfLastStatement(node as estree.ReturnStatement); 75 | }, 76 | 77 | ":function > BlockStatement > IfStatement > BlockStatement > ReturnStatement": (node: estree.Node) => { 78 | reportIfLastStatementInsideIf(node as estree.ReturnStatement); 79 | }, 80 | }; 81 | }, 82 | }; 83 | 84 | export = rule; 85 | -------------------------------------------------------------------------------- /src/rules/no-same-line-conditional.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-3972 21 | 22 | import { AST, Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { toEncodedMessage } from "../utils/locations"; 25 | 26 | interface SiblingIfStatement { 27 | first: estree.IfStatement; 28 | following: estree.IfStatement; 29 | } 30 | 31 | const rule: Rule.RuleModule = { 32 | meta: { 33 | type: "problem", 34 | docs: { 35 | description: "Conditionals should start on new lines", 36 | category: "Code Smell Detection", 37 | recommended: true, 38 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-same-line-conditional.md", 39 | }, 40 | schema: [ 41 | { 42 | // internal parameter 43 | enum: ["radar-runtime"], 44 | }, 45 | ], 46 | }, 47 | create(context: Rule.RuleContext) { 48 | function checkStatements(statements: estree.Node[]) { 49 | const sourceCode = context.getSourceCode(); 50 | const siblingIfStatements = getSiblingIfStatements(statements); 51 | 52 | siblingIfStatements.forEach((siblingIfStatement) => { 53 | const precedingIf = siblingIfStatement.first; 54 | const followingIf = siblingIfStatement.following; 55 | if ( 56 | !!precedingIf.loc && 57 | !!followingIf.loc && 58 | precedingIf.loc.end.line === followingIf.loc.start.line && 59 | precedingIf.loc.start.line !== followingIf.loc.end.line 60 | ) { 61 | const precedingIfLastToken = sourceCode.getLastToken(precedingIf) as AST.Token; 62 | const followingIfToken = sourceCode.getFirstToken(followingIf) as AST.Token; 63 | context.report({ 64 | message: toEncodedMessage(`Move this "if" to a new line or add the missing "else".`, [ 65 | precedingIfLastToken, 66 | ]), 67 | loc: followingIfToken.loc, 68 | }); 69 | } 70 | }); 71 | } 72 | 73 | return { 74 | Program: (node: estree.Node) => checkStatements((node as estree.Program).body), 75 | BlockStatement: (node: estree.Node) => checkStatements((node as estree.BlockStatement).body), 76 | SwitchCase: (node: estree.Node) => checkStatements((node as estree.SwitchCase).consequent), 77 | }; 78 | }, 79 | }; 80 | 81 | function getSiblingIfStatements(statements: estree.Node[]): SiblingIfStatement[] { 82 | return statements.reduce((siblingsArray, statement, currentIndex) => { 83 | const previousStatement = statements[currentIndex - 1]; 84 | if (statement.type === "IfStatement" && !!previousStatement && previousStatement.type === "IfStatement") { 85 | return [{ first: previousStatement, following: statement }, ...siblingsArray]; 86 | } 87 | return siblingsArray; 88 | }, []); 89 | } 90 | 91 | export = rule; 92 | -------------------------------------------------------------------------------- /src/rules/no-small-switch.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1301 21 | 22 | import { Rule } from "eslint"; 23 | import { Node, SwitchStatement } from "estree"; 24 | 25 | const MESSAGE = '"switch" statements should have at least 3 "case" clauses'; 26 | 27 | const rule: Rule.RuleModule = { 28 | meta: { 29 | type: "suggestion", 30 | docs: { 31 | description: '"switch" statements should have at least 3 "case" clauses', 32 | category: "Code Smell Detection", 33 | recommended: true, 34 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-small-switch.md", 35 | }, 36 | }, 37 | create(context: Rule.RuleContext) { 38 | return { 39 | SwitchStatement(node: Node) { 40 | const switchStatement = node as SwitchStatement; 41 | const { cases } = switchStatement; 42 | const hasDefault = cases.some((x) => !x.test); 43 | if (cases.length < 2 || (cases.length === 2 && hasDefault)) { 44 | const firstToken = context.getSourceCode().getFirstToken(node); 45 | if (firstToken) { 46 | context.report({ message: MESSAGE, loc: firstToken.loc }); 47 | } 48 | } 49 | }, 50 | }; 51 | }, 52 | }; 53 | 54 | export = rule; 55 | -------------------------------------------------------------------------------- /src/rules/no-use-of-empty-return-value.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-3699 21 | 22 | import { Rule } from "eslint"; 23 | import { Node, CallExpression, Function, ReturnStatement, Identifier, ArrowFunctionExpression } from "estree"; 24 | import { isFunctionExpression, isArrowFunctionExpression, isBlockStatement, getParent } from "../utils/nodes"; 25 | 26 | function isReturnValueUsed(callExpr: Node, context: Rule.RuleContext) { 27 | const parent = getParent(context); 28 | if (!parent) { 29 | return false; 30 | } 31 | 32 | if (parent.type === "LogicalExpression") { 33 | return parent.left === callExpr; 34 | } 35 | 36 | if (parent.type === "SequenceExpression") { 37 | return parent.expressions[parent.expressions.length - 1] === callExpr; 38 | } 39 | 40 | if (parent.type === "ConditionalExpression") { 41 | return parent.test === callExpr; 42 | } 43 | 44 | return ( 45 | parent.type !== "ExpressionStatement" && 46 | parent.type !== "ArrowFunctionExpression" && 47 | parent.type !== "UnaryExpression" && 48 | parent.type !== "AwaitExpression" && 49 | parent.type !== "ReturnStatement" && 50 | parent.type !== "ThrowStatement" 51 | ); 52 | } 53 | 54 | const rule: Rule.RuleModule = { 55 | meta: { 56 | type: "problem", 57 | docs: { 58 | description: "The output of functions that don't return anything should not be used", 59 | category: "Bug Detection", 60 | recommended: true, 61 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-use-of-empty-return-value.md", 62 | }, 63 | }, 64 | create(context: Rule.RuleContext) { 65 | const callExpressionsToCheck: Map = new Map(); 66 | const functionsWithReturnValue: Set = new Set(); 67 | 68 | return { 69 | CallExpression(node: Node) { 70 | const callExpr = node as CallExpression; 71 | if (!isReturnValueUsed(callExpr, context)) { 72 | return; 73 | } 74 | const scope = context.getScope(); 75 | const reference = scope.references.find((ref) => ref.identifier === callExpr.callee); 76 | if (reference && reference.resolved) { 77 | const variable = reference.resolved; 78 | if (variable.defs.length === 1) { 79 | const definition = variable.defs[0]; 80 | if (definition.type === "FunctionName") { 81 | callExpressionsToCheck.set(reference.identifier, definition.node); 82 | } else if (definition.type === "Variable") { 83 | const { init } = definition.node; 84 | if (init && (isFunctionExpression(init) || isArrowFunctionExpression(init))) { 85 | callExpressionsToCheck.set(reference.identifier, init); 86 | } 87 | } 88 | } 89 | } 90 | }, 91 | 92 | ReturnStatement(node: Node) { 93 | const returnStmt = node as ReturnStatement; 94 | if (returnStmt.argument) { 95 | const ancestors = [...context.getAncestors()].reverse(); 96 | const functionNode = ancestors.find( 97 | (node) => 98 | node.type === "FunctionExpression" || 99 | node.type === "FunctionDeclaration" || 100 | node.type === "ArrowFunctionExpression", 101 | ); 102 | 103 | functionsWithReturnValue.add(functionNode as Function); 104 | } 105 | }, 106 | 107 | ArrowFunctionExpression(node: Node) { 108 | const arrowFunc = node as ArrowFunctionExpression; 109 | if (arrowFunc.expression) { 110 | functionsWithReturnValue.add(arrowFunc); 111 | } 112 | }, 113 | 114 | ":function"(node: Node) { 115 | const func = node as Function; 116 | if (func.async || func.generator || (isBlockStatement(func.body) && func.body.body.length === 0)) { 117 | functionsWithReturnValue.add(func); 118 | } 119 | }, 120 | 121 | "Program:exit"() { 122 | callExpressionsToCheck.forEach((functionDeclaration, callee) => { 123 | if (!functionsWithReturnValue.has(functionDeclaration)) { 124 | context.report({ 125 | message: `Remove this use of the output from "{{name}}"; "{{name}}" doesn't return anything.`, 126 | node: callee, 127 | data: { name: callee.name }, 128 | }); 129 | } 130 | }); 131 | }, 132 | }; 133 | }, 134 | }; 135 | 136 | export = rule; 137 | -------------------------------------------------------------------------------- /src/rules/no-useless-catch.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1940 21 | 22 | import { Rule, SourceCode } from "eslint"; 23 | import { Node, CatchClause, Statement, Pattern } from "estree"; 24 | import { isThrowStatement } from "../utils/nodes"; 25 | import { areEquivalent } from "../utils/equivalence"; 26 | 27 | const MESSAGE = "Add logic to this catch clause or eliminate it and rethrow the exception automatically."; 28 | 29 | const rule: Rule.RuleModule = { 30 | meta: { 31 | type: "suggestion", 32 | docs: { 33 | description: '"catch" clauses should do more than rethrow', 34 | category: "Code Smell Detection", 35 | recommended: true, 36 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/no-useless-catch.md", 37 | }, 38 | }, 39 | create(context: Rule.RuleContext) { 40 | return { CatchClause: (node: Node) => visitCatchClause(node as CatchClause, context) }; 41 | }, 42 | }; 43 | 44 | function visitCatchClause(catchClause: CatchClause, context: Rule.RuleContext) { 45 | const statements = catchClause.body.body; 46 | if ( 47 | catchClause.param && 48 | statements.length === 1 && 49 | onlyRethrows(statements[0], catchClause.param, context.getSourceCode()) 50 | ) { 51 | const catchKeyword = context.getSourceCode().getFirstToken(catchClause)!; 52 | context.report({ 53 | message: MESSAGE, 54 | loc: catchKeyword.loc, 55 | }); 56 | } 57 | } 58 | 59 | function onlyRethrows(statement: Statement, catchParam: Pattern, sourceCode: SourceCode) { 60 | return isThrowStatement(statement) && areEquivalent(catchParam, statement.argument, sourceCode); 61 | } 62 | 63 | export = rule; 64 | -------------------------------------------------------------------------------- /src/rules/prefer-immediate-return.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1488 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { isReturnStatement, isThrowStatement, isIdentifier, isVariableDeclaration } from "../utils/nodes"; 25 | 26 | const rule: Rule.RuleModule = { 27 | meta: { 28 | type: "suggestion", 29 | docs: { 30 | description: "Local variables should not be declared and then immediately returned or thrown", 31 | category: "Code Smell Detection", 32 | recommended: true, 33 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/prefer-immediate-return.md", 34 | }, 35 | fixable: "code", 36 | }, 37 | create(context: Rule.RuleContext) { 38 | return { 39 | BlockStatement(node: estree.Node) { 40 | processStatements((node as estree.BlockStatement).body); 41 | }, 42 | SwitchCase(node: estree.Node) { 43 | processStatements((node as estree.SwitchCase).consequent); 44 | }, 45 | }; 46 | 47 | function processStatements(statements: estree.Statement[]) { 48 | if (statements.length > 1) { 49 | const last = statements[statements.length - 1]; 50 | const returnedIdentifier = getOnlyReturnedVariable(last); 51 | 52 | const lastButOne = statements[statements.length - 2]; 53 | const declaredIdentifier = getOnlyDeclaredVariable(lastButOne); 54 | 55 | if (returnedIdentifier && declaredIdentifier) { 56 | const sameVariable = getVariables(context).find((variable) => { 57 | return ( 58 | variable.references.find((ref) => ref.identifier === returnedIdentifier) !== undefined && 59 | variable.references.find((ref) => ref.identifier === declaredIdentifier.id) !== undefined 60 | ); 61 | }); 62 | 63 | // there must be only one "read" - in `return` or `throw` 64 | if (sameVariable && sameVariable.references.filter((ref) => ref.isRead()).length === 1) { 65 | context.report({ 66 | message: formatMessage(last, returnedIdentifier.name), 67 | node: declaredIdentifier.init, 68 | fix: (fixer) => fix(fixer, last, lastButOne, declaredIdentifier.init), 69 | }); 70 | } 71 | } 72 | } 73 | } 74 | 75 | function fix( 76 | fixer: Rule.RuleFixer, 77 | last: estree.Statement, 78 | lastButOne: estree.Statement, 79 | expression: estree.Expression, 80 | ): any { 81 | const throwOrReturnKeyword = context.getSourceCode().getFirstToken(last); 82 | 83 | if (lastButOne.range && last.range && throwOrReturnKeyword) { 84 | const expressionText = context.getSourceCode().getText(expression); 85 | const fixedRangeStart = lastButOne.range[0]; 86 | const fixedRangeEnd = last.range[1]; 87 | const semicolonToken = context.getSourceCode().getLastToken(last); 88 | const semicolon = semicolonToken && semicolonToken.value === ";" ? ";" : ""; 89 | return [ 90 | fixer.removeRange([fixedRangeStart, fixedRangeEnd]), 91 | fixer.insertTextAfterRange( 92 | [1, fixedRangeStart], 93 | `${throwOrReturnKeyword.value} ${expressionText}${semicolon}`, 94 | ), 95 | ]; 96 | } else { 97 | return null; 98 | } 99 | } 100 | 101 | function getOnlyReturnedVariable(node: estree.Statement) { 102 | return (isReturnStatement(node) || isThrowStatement(node)) && node.argument && isIdentifier(node.argument) 103 | ? node.argument 104 | : undefined; 105 | } 106 | 107 | function getOnlyDeclaredVariable(node: estree.Statement) { 108 | if (isVariableDeclaration(node) && node.declarations.length === 1) { 109 | const { id, init } = node.declarations[0]; 110 | if (isIdentifier(id) && init) { 111 | return { id, init }; 112 | } 113 | } 114 | return undefined; 115 | } 116 | 117 | function formatMessage(node: estree.Node, variable: string) { 118 | const action = isReturnStatement(node) ? "return" : "throw"; 119 | return `Immediately ${action} this expression instead of assigning it to the temporary variable "${variable}".`; 120 | } 121 | 122 | function getVariables(context: Rule.RuleContext) { 123 | const { variableScope, variables: currentScopeVariables } = context.getScope(); 124 | if (variableScope === context.getScope()) { 125 | return currentScopeVariables; 126 | } else { 127 | return currentScopeVariables.concat(variableScope.variables); 128 | } 129 | } 130 | }, 131 | }; 132 | 133 | export = rule; 134 | -------------------------------------------------------------------------------- /src/rules/prefer-object-literal.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-2428 21 | 22 | import { Rule, SourceCode } from "eslint"; 23 | import { Node, Statement, Program, Identifier, BlockStatement, Expression } from "estree"; 24 | import { 25 | isModuleDeclaration, 26 | isVariableDeclaration, 27 | isObjectExpression, 28 | isExpressionStatement, 29 | isAssignmentExpression, 30 | isMemberExpression, 31 | isIdentifier, 32 | } from "../utils/nodes"; 33 | import { areEquivalent } from "../utils/equivalence"; 34 | 35 | const MESSAGE = 36 | "Declare one or more properties of this object inside of the object literal syntax instead of using separate statements."; 37 | 38 | const rule: Rule.RuleModule = { 39 | meta: { 40 | type: "suggestion", 41 | docs: { 42 | description: "Object literal syntax should be used", 43 | category: "Code Smell Detection", 44 | recommended: true, 45 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/prefer-object-literal.md", 46 | }, 47 | }, 48 | create(context: Rule.RuleContext) { 49 | return { 50 | BlockStatement: (node: Node) => checkObjectInitialization((node as BlockStatement).body, context), 51 | Program: (node: Node) => { 52 | const statements = (node as Program).body.filter( 53 | (statement): statement is Statement => !isModuleDeclaration(statement), 54 | ); 55 | checkObjectInitialization(statements, context); 56 | }, 57 | }; 58 | }, 59 | }; 60 | 61 | function checkObjectInitialization(statements: Statement[], context: Rule.RuleContext) { 62 | let index = 0; 63 | while (index < statements.length - 1) { 64 | const objectDeclaration = getObjectDeclaration(statements[index]); 65 | // eslint-disable-next-line radar/no-collapsible-if 66 | if (objectDeclaration && isIdentifier(objectDeclaration.id)) { 67 | if (isPropertyAssignment(statements[index + 1], objectDeclaration.id, context.getSourceCode())) { 68 | context.report({ message: MESSAGE, node: objectDeclaration }); 69 | } 70 | } 71 | index++; 72 | } 73 | } 74 | 75 | function getObjectDeclaration(statement: Statement) { 76 | if (isVariableDeclaration(statement)) { 77 | return statement.declarations.find( 78 | (declaration) => !!declaration.init && isEmptyObjectExpression(declaration.init), 79 | ); 80 | } 81 | return undefined; 82 | } 83 | 84 | function isEmptyObjectExpression(expression: Expression) { 85 | return isObjectExpression(expression) && expression.properties.length === 0; 86 | } 87 | 88 | function isPropertyAssignment(statement: Statement, objectIdentifier: Identifier, sourceCode: SourceCode) { 89 | if (isExpressionStatement(statement) && isAssignmentExpression(statement.expression)) { 90 | const { left, right } = statement.expression; 91 | if (isMemberExpression(left)) { 92 | return ( 93 | !left.computed && 94 | isSingleLineExpression(right, sourceCode) && 95 | areEquivalent(left.object, objectIdentifier, sourceCode) 96 | ); 97 | } 98 | } 99 | return false; 100 | } 101 | 102 | function isSingleLineExpression(expression: Expression, sourceCode: SourceCode) { 103 | const first = sourceCode.getFirstToken(expression)!.loc; 104 | const last = sourceCode.getLastToken(expression)!.loc; 105 | return first.start.line === last.end.line; 106 | } 107 | 108 | export = rule; 109 | -------------------------------------------------------------------------------- /src/rules/prefer-single-boolean-return.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1126 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | import { isReturnStatement, isBlockStatement, isBooleanLiteral, isIfStatement, getParent } from "../utils/nodes"; 25 | 26 | const rule: Rule.RuleModule = { 27 | meta: { 28 | type: "suggestion", 29 | docs: { 30 | description: 'Return of boolean expressions should not be wrapped into an "if-then-else" statement', 31 | category: "Code Smell Detection", 32 | recommended: true, 33 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/prefer-single-boolean-return.md", 34 | }, 35 | }, 36 | create(context: Rule.RuleContext) { 37 | return { 38 | IfStatement(node: estree.Node) { 39 | const ifStmt = node as estree.IfStatement; 40 | if ( 41 | // ignore `else if` 42 | !isIfStatement(getParent(context)) && 43 | // `ifStmt.alternate` can be `null`, replace it with `undefined` in this case 44 | returnsBoolean(ifStmt.alternate || undefined) && 45 | returnsBoolean(ifStmt.consequent) 46 | ) { 47 | context.report({ 48 | message: "Replace this if-then-else statement by a single return statement.", 49 | node: ifStmt, 50 | }); 51 | } 52 | }, 53 | }; 54 | 55 | function returnsBoolean(statement: estree.Statement | undefined) { 56 | return ( 57 | statement !== undefined && 58 | (isBlockReturningBooleanLiteral(statement) || isSimpleReturnBooleanLiteral(statement)) 59 | ); 60 | } 61 | 62 | function isBlockReturningBooleanLiteral(statement: estree.Statement) { 63 | return ( 64 | isBlockStatement(statement) && statement.body.length === 1 && isSimpleReturnBooleanLiteral(statement.body[0]) 65 | ); 66 | } 67 | 68 | function isSimpleReturnBooleanLiteral(statement: estree.Statement) { 69 | // `statement.argument` can be `null`, replace it with `undefined` in this case 70 | return isReturnStatement(statement) && isBooleanLiteral(statement.argument || undefined); 71 | } 72 | }, 73 | }; 74 | 75 | export = rule; 76 | -------------------------------------------------------------------------------- /src/rules/prefer-while.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | // https://jira.sonarsource.com/browse/RSPEC-1264 21 | 22 | import { Rule } from "eslint"; 23 | import * as estree from "estree"; 24 | 25 | const rule: Rule.RuleModule = { 26 | meta: { 27 | type: "suggestion", 28 | docs: { 29 | description: 'A "while" loop should be used instead of a "for" loop', 30 | category: "Code Smell Detection", 31 | recommended: true, 32 | url: "https://github.com/es-joy/eslint-plugin-radar/blob/master/docs/rules/prefer-while.md", 33 | }, 34 | fixable: "code", 35 | }, 36 | create(context: Rule.RuleContext) { 37 | return { 38 | ForStatement(node: estree.Node) { 39 | const forLoop = node as estree.ForStatement; 40 | const forKeyword = context.getSourceCode().getFirstToken(node); 41 | if (!forLoop.init && !forLoop.update && forKeyword) { 42 | context.report({ 43 | message: `Replace this "for" loop with a "while" loop.`, 44 | loc: forKeyword.loc, 45 | fix: getFix(forLoop), 46 | }); 47 | } 48 | }, 49 | }; 50 | 51 | function getFix(forLoop: estree.ForStatement): any { 52 | const forLoopRange = forLoop.range; 53 | const closingParenthesisToken = context.getSourceCode().getTokenBefore(forLoop.body); 54 | const condition = forLoop.test; 55 | 56 | if (condition && forLoopRange && closingParenthesisToken) { 57 | return (fixer: Rule.RuleFixer) => { 58 | const start = forLoopRange[0]; 59 | const end = closingParenthesisToken.range[1]; 60 | const conditionText = context.getSourceCode().getText(condition); 61 | return fixer.replaceTextRange([start, end], `while (${conditionText})`); 62 | }; 63 | } 64 | 65 | return undefined; 66 | } 67 | }, 68 | }; 69 | 70 | export = rule; 71 | -------------------------------------------------------------------------------- /src/utils/collections.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | export const collectionConstructor = ["Array", "Map", "Set", "WeakSet", "WeakMap"]; 21 | 22 | export const writingMethods = [ 23 | // array methods 24 | "copyWithin", 25 | "fill", 26 | "pop", 27 | "push", 28 | "reverse", 29 | "set", 30 | "shift", 31 | "sort", 32 | "splice", 33 | "unshift", 34 | // map, set methods 35 | "add", 36 | "clear", 37 | "delete", 38 | ]; 39 | -------------------------------------------------------------------------------- /src/utils/conditions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import * as estree from "estree"; 21 | import { isIfStatement } from "./nodes"; 22 | 23 | /** Returns a list of statements corresponding to a `if - else if - else` chain */ 24 | export function collectIfBranches(node: estree.IfStatement) { 25 | const branches: estree.Statement[] = [node.consequent]; 26 | let endsWithElse = false; 27 | let statement = node.alternate; 28 | 29 | while (statement) { 30 | if (isIfStatement(statement)) { 31 | branches.push(statement.consequent); 32 | statement = statement.alternate; 33 | } else { 34 | branches.push(statement); 35 | endsWithElse = true; 36 | break; 37 | } 38 | } 39 | 40 | return { branches, endsWithElse }; 41 | } 42 | 43 | /** Returns a list of `switch` clauses (both `case` and `default`) */ 44 | export function collectSwitchBranches(node: estree.SwitchStatement) { 45 | let endsWithDefault = false; 46 | const branches = node.cases 47 | .filter((clause, index) => { 48 | if (!clause.test) { 49 | endsWithDefault = true; 50 | } 51 | // if a branch has no implementation, it's fall-through and it should not be considered 52 | // the only exception is the last case 53 | const isLast = index === node.cases.length - 1; 54 | return isLast || clause.consequent.length > 0; 55 | }) 56 | .map((clause) => takeWithoutBreak(clause.consequent)); 57 | return { branches, endsWithDefault }; 58 | } 59 | 60 | /** Excludes the break statement from the list */ 61 | export function takeWithoutBreak(nodes: estree.Statement[]) { 62 | return nodes.length > 0 && nodes[nodes.length - 1].type === "BreakStatement" ? nodes.slice(0, -1) : nodes; 63 | } 64 | -------------------------------------------------------------------------------- /src/utils/equivalence.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { Node } from "estree"; 21 | import { SourceCode, AST } from "eslint"; 22 | 23 | /** 24 | * Equivalence is implemented by comparing node types and their tokens. 25 | * Classic implementation would recursively compare children, 26 | * but "estree" doesn't provide access to children when node type is unknown 27 | */ 28 | export function areEquivalent(first: Node | Node[], second: Node | Node[], sourceCode: SourceCode): boolean { 29 | if (Array.isArray(first) && Array.isArray(second)) { 30 | return ( 31 | first.length === second.length && 32 | first.every((firstNode, index) => areEquivalent(firstNode, second[index], sourceCode)) 33 | ); 34 | } else if (!Array.isArray(first) && !Array.isArray(second)) { 35 | return first.type === second.type && compareTokens(sourceCode.getTokens(first), sourceCode.getTokens(second)); 36 | } 37 | return false; 38 | } 39 | 40 | function compareTokens(firstTokens: AST.Token[], secondTokens: AST.Token[]) { 41 | return ( 42 | firstTokens.length === secondTokens.length && 43 | firstTokens.every((firstToken, index) => firstToken.value === secondTokens[index].value) 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/locations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { Rule, AST } from "eslint"; 21 | import * as estree from "estree"; 22 | 23 | export interface IssueLocation { 24 | column: number; 25 | line: number; 26 | endColumn: number; 27 | endLine: number; 28 | message?: string; 29 | } 30 | 31 | export interface EncodedMessage { 32 | message: string; 33 | cost?: number; 34 | secondaryLocations: IssueLocation[]; 35 | } 36 | 37 | /** 38 | * Returns a location of the "main" function token: 39 | * - function name for a function declaration, method or accessor 40 | * - "function" keyword for a function expression 41 | * - "=>" for an arrow function 42 | */ 43 | export function getMainFunctionTokenLocation( 44 | fn: estree.Function, 45 | parent: estree.Node | undefined, 46 | context: Rule.RuleContext, 47 | ) { 48 | let location: estree.SourceLocation | null | undefined; 49 | 50 | if (fn.type === "FunctionDeclaration") { 51 | // `fn.id` can be null when it is `export default function` (despite of the @types/estree definition) 52 | if (fn.id) { 53 | location = fn.id.loc; 54 | } else { 55 | const token = getTokenByValue(fn, "function", context); 56 | location = token && token.loc; 57 | } 58 | } else if (fn.type === "FunctionExpression") { 59 | if (parent && (parent.type === "MethodDefinition" || parent.type === "Property")) { 60 | location = parent.key.loc; 61 | } else { 62 | const token = getTokenByValue(fn, "function", context); 63 | location = token && token.loc; 64 | } 65 | } else if (fn.type === "ArrowFunctionExpression") { 66 | const token = context 67 | .getSourceCode() 68 | .getTokensBefore(fn.body) 69 | .reverse() 70 | .find((token) => token.value === "=>"); 71 | 72 | location = token && token.loc; 73 | } 74 | 75 | return location!; 76 | } 77 | 78 | // As `ReportDescriptor` may contain either `message` or `messageId` prop, 79 | // we force the presence of `message` property by using the following type alias. 80 | export type ReportDescriptor = Rule.ReportDescriptor & { message: string }; 81 | 82 | /** 83 | * Wrapper for `context.report`, supporting secondary locations and cost. 84 | * Encode those extra information in the issue message when rule is executed 85 | * in Radar* environment. 86 | */ 87 | export function report( 88 | context: Rule.RuleContext, 89 | reportDescriptor: ReportDescriptor, 90 | secondaryLocations: IssueLocation[] = [], 91 | cost?: number, 92 | ) { 93 | const { message } = reportDescriptor; 94 | if (context.options[context.options.length - 1] === "radar-runtime") { 95 | const encodedMessage: EncodedMessage = { secondaryLocations, message, cost }; 96 | reportDescriptor.message = JSON.stringify(encodedMessage); 97 | } 98 | context.report(reportDescriptor); 99 | } 100 | 101 | /** 102 | * Converts `SourceLocation` range into `IssueLocation` 103 | */ 104 | export function issueLocation( 105 | startLoc: estree.SourceLocation, 106 | endLoc: estree.SourceLocation = startLoc, 107 | message = "", 108 | ): IssueLocation { 109 | return { 110 | line: startLoc.start.line, 111 | column: startLoc.start.column, 112 | endLine: endLoc.end.line, 113 | endColumn: endLoc.end.column, 114 | message, 115 | }; 116 | } 117 | 118 | export function toEncodedMessage( 119 | message: string, 120 | secondaryLocationsHolder: Array, 121 | secondaryMessages?: string[], 122 | cost?: number, 123 | ): string { 124 | const encodedMessage: EncodedMessage = { 125 | message, 126 | cost, 127 | secondaryLocations: secondaryLocationsHolder.map((locationHolder, index) => 128 | toSecondaryLocation(locationHolder, secondaryMessages ? secondaryMessages[index] : undefined), 129 | ), 130 | }; 131 | return JSON.stringify(encodedMessage); 132 | } 133 | 134 | function toSecondaryLocation(locationHolder: AST.Token | estree.Node, message?: string): IssueLocation { 135 | const loc = locationHolder.loc!; 136 | return { 137 | message, 138 | column: loc.start.column, 139 | line: loc.start.line, 140 | endColumn: loc.end.column, 141 | endLine: loc.end.line, 142 | }; 143 | } 144 | 145 | function getTokenByValue(node: estree.Node, value: string, context: Rule.RuleContext) { 146 | return context 147 | .getSourceCode() 148 | .getTokens(node) 149 | .find((token) => token.value === value); 150 | } 151 | 152 | export function getFirstTokenAfter(node: estree.Node, context: Rule.RuleContext): AST.Token | null { 153 | return context.getSourceCode().getTokenAfter(node); 154 | } 155 | 156 | export function getFirstToken(node: estree.Node, context: Rule.RuleContext): AST.Token { 157 | return context.getSourceCode().getTokens(node)[0]; 158 | } 159 | -------------------------------------------------------------------------------- /src/utils/nodes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { Rule } from "eslint"; 21 | import * as estree from "estree"; 22 | 23 | const MODULE_DECLARATION_NODES = [ 24 | "ImportDeclaration", 25 | "ExportNamedDeclaration", 26 | "ExportDefaultDeclaration", 27 | "ExportAllDeclaration", 28 | ]; 29 | 30 | export function getParent(context: Rule.RuleContext) { 31 | const ancestors = context.getAncestors(); 32 | return ancestors.length > 0 ? ancestors[ancestors.length - 1] : undefined; 33 | } 34 | 35 | export function isArrowFunctionExpression(node: estree.Node | undefined): node is estree.ArrowFunctionExpression { 36 | return node !== undefined && node.type === "ArrowFunctionExpression"; 37 | } 38 | 39 | export function isAssignmentExpression(node: estree.Node | undefined): node is estree.AssignmentExpression { 40 | return node !== undefined && node.type === "AssignmentExpression"; 41 | } 42 | 43 | export function isBinaryExpression(node: estree.Node | undefined): node is estree.BinaryExpression { 44 | return node !== undefined && node.type === "BinaryExpression"; 45 | } 46 | 47 | export function isBlockStatement(node: estree.Node | undefined): node is estree.BlockStatement { 48 | return node !== undefined && node.type === "BlockStatement"; 49 | } 50 | 51 | export function isBooleanLiteral(node: estree.Node | undefined): node is estree.Literal { 52 | return isLiteral(node) && typeof node.value === "boolean"; 53 | } 54 | 55 | export function isCallExpression(node: estree.Node | undefined): node is estree.CallExpression { 56 | return node !== undefined && node.type === "CallExpression"; 57 | } 58 | 59 | export function isConditionalExpression(node: estree.Node | undefined): node is estree.ConditionalExpression { 60 | return node !== undefined && node.type === "ConditionalExpression"; 61 | } 62 | 63 | export function isContinueStatement(node: estree.Node | undefined): node is estree.ContinueStatement { 64 | return node !== undefined && node.type === "ContinueStatement"; 65 | } 66 | 67 | export function isExpressionStatement(node: estree.Node | undefined): node is estree.ExpressionStatement { 68 | return node !== undefined && node.type === "ExpressionStatement"; 69 | } 70 | 71 | export function isFunctionDeclaration(node: estree.Node | undefined): node is estree.FunctionDeclaration { 72 | return node !== undefined && node.type === "FunctionDeclaration"; 73 | } 74 | 75 | export function isFunctionExpression(node: estree.Node | undefined): node is estree.FunctionExpression { 76 | return node !== undefined && node.type === "FunctionExpression"; 77 | } 78 | 79 | export function isIdentifier(node: estree.Node | undefined): node is estree.Identifier { 80 | return node !== undefined && node.type === "Identifier"; 81 | } 82 | 83 | export function isIfStatement(node: estree.Node | undefined): node is estree.IfStatement { 84 | return node !== undefined && node.type === "IfStatement"; 85 | } 86 | 87 | export function isLiteral(node: estree.Node | undefined): node is estree.Literal { 88 | return node !== undefined && node.type === "Literal"; 89 | } 90 | 91 | export function isLogicalExpression(node: estree.Node | undefined): node is estree.LogicalExpression { 92 | return node !== undefined && node.type === "LogicalExpression"; 93 | } 94 | 95 | export function isMemberExpression(node: estree.Node | undefined): node is estree.MemberExpression { 96 | return node !== undefined && node.type === "MemberExpression"; 97 | } 98 | 99 | export function isModuleDeclaration(node: estree.Node | undefined): node is estree.ModuleDeclaration { 100 | return node !== undefined && MODULE_DECLARATION_NODES.includes(node.type); 101 | } 102 | 103 | export function isObjectExpression(node: estree.Node | undefined): node is estree.ObjectExpression { 104 | return node !== undefined && node.type === "ObjectExpression"; 105 | } 106 | 107 | export function isReturnStatement(node: estree.Node | undefined): node is estree.ReturnStatement { 108 | return node !== undefined && node.type === "ReturnStatement"; 109 | } 110 | 111 | export function isThrowStatement(node: estree.Node | undefined): node is estree.ThrowStatement { 112 | return node !== undefined && node.type === "ThrowStatement"; 113 | } 114 | 115 | export function isVariableDeclaration(node: estree.Node | undefined): node is estree.VariableDeclaration { 116 | return node !== undefined && node.type === "VariableDeclaration"; 117 | } 118 | -------------------------------------------------------------------------------- /src/utils/parser-services.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { ParserServices } from "@typescript-eslint/parser"; 21 | 22 | export type RequiredParserServices = { [k in keyof ParserServices]: Exclude }; 23 | 24 | export function isRequiredParserServices(services: ParserServices): services is RequiredParserServices { 25 | return !!services && !!services.program && !!services.esTreeNodeToTSNodeMap; 26 | } 27 | -------------------------------------------------------------------------------- /tests/index.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import * as fs from "fs"; 21 | import * as path from "path"; 22 | import { configs, rules } from "../src/index"; 23 | 24 | const rulesPath = path.join(__dirname, "../src/rules"); 25 | const existingRules = fs.readdirSync(rulesPath).map((file) => file.substring(0, file.indexOf(".ts"))); 26 | 27 | it("should declare all rules in recommended config", () => { 28 | existingRules.forEach((rule) => { 29 | expect(configs.recommended.rules).toHaveProperty(`radar/${rule}`); 30 | }); 31 | expect(Object.keys(configs.recommended.rules!)).toHaveLength(existingRules.length); 32 | }); 33 | 34 | it("should declare all rules", () => { 35 | existingRules.forEach((rule) => { 36 | expect(rules).toHaveProperty(rule); 37 | }); 38 | expect(Object.keys(rules)).toHaveLength(existingRules.length); 39 | }); 40 | 41 | it("should document all rules", () => { 42 | const root = path.join(__dirname, "../"); 43 | const README = fs.readFileSync(`${root}/README.md`, "utf8"); 44 | existingRules.forEach((rule) => { 45 | expect(README.includes(rule)).toBe(true); 46 | expect(fs.existsSync(`${root}/docs/rules/${rule}.md`)).toBe(true); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/resources/file.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/es-joy/eslint-plugin-radar/f48157648b410c31419a77ed336d84f1d702cfdb/tests/resources/file.ts -------------------------------------------------------------------------------- /tests/resources/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018" 4 | }, 5 | "include": ["./file.ts"] 6 | } 7 | -------------------------------------------------------------------------------- /tests/rules/max-switch-cases.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/max-switch-cases"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); 24 | 25 | ruleTester.run("max-switch-cases", rule, { 26 | valid: [ 27 | { 28 | code: `switch(i) { 29 | case 1: 30 | f(); 31 | case 2: 32 | g(); 33 | }`, 34 | }, 35 | // default branch is excluded 36 | { 37 | code: `switch(i) { 38 | case 1: 39 | f(); 40 | case 2: 41 | g(); 42 | default: 43 | console.log("foo"); 44 | }`, 45 | options: [2], 46 | }, 47 | // empty branches are not counted 48 | { 49 | code: `switch(i) { 50 | case 1: 51 | case 2: 52 | g(); 53 | case 3: 54 | console.log("foo"); 55 | }`, 56 | options: [2], 57 | }, 58 | // empty switch statement 59 | { 60 | code: `switch(i) {}`, 61 | }, 62 | ], 63 | invalid: [ 64 | { 65 | code: `switch(i) { 66 | case 1: 67 | f(); 68 | case 2: 69 | g(); 70 | }`, 71 | options: [1], 72 | errors: [ 73 | { 74 | message: message(2, 1), 75 | line: 1, 76 | endLine: 1, 77 | column: 1, 78 | endColumn: 7, 79 | }, 80 | ], 81 | }, 82 | ], 83 | }); 84 | 85 | function message(numSwitchCases: number, maxSwitchCases: number) { 86 | return `Reduce the number of non-empty switch cases from ${numSwitchCases} to at most ${maxSwitchCases}.`; 87 | } 88 | -------------------------------------------------------------------------------- /tests/rules/no-collapsible-if.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-collapsible-if"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); 24 | 25 | ruleTester.run("no-collapsible-if", rule, { 26 | valid: [ 27 | { 28 | code: ` 29 | if (x) { 30 | console.log(x); 31 | }`, 32 | }, 33 | { 34 | code: ` 35 | if (x) { 36 | if (y) {} 37 | console.log(x); 38 | }`, 39 | }, 40 | { 41 | code: ` 42 | if (x) { 43 | console.log(x); 44 | if (y) {} 45 | }`, 46 | }, 47 | { 48 | code: ` 49 | if (x) { 50 | if (y) {} 51 | } else {}`, 52 | }, 53 | { 54 | code: ` 55 | if (x) { 56 | if (y) {} else {} 57 | }`, 58 | }, 59 | ], 60 | 61 | invalid: [ 62 | { 63 | code: ` 64 | if (x) { 65 | //^^ > {{Merge this if statement with the nested one.}} 66 | if (y) {} 67 | //^^ {{Nested "if" statement.}} 68 | }`, 69 | options: ["radar-runtime"], 70 | errors: [ 71 | { 72 | message: JSON.stringify({ 73 | secondaryLocations: [ 74 | { 75 | line: 4, 76 | column: 8, 77 | endLine: 4, 78 | endColumn: 10, 79 | message: `Nested "if" statement.`, 80 | }, 81 | ], 82 | message: "Merge this if statement with the nested one.", 83 | }), 84 | line: 2, 85 | column: 7, 86 | endLine: 2, 87 | endColumn: 9, 88 | }, 89 | ], 90 | }, 91 | { 92 | code: ` 93 | if (x) 94 | if(y) {}`, 95 | errors: [{ message: "Merge this if statement with the nested one." }], 96 | }, 97 | { 98 | code: ` 99 | if (x) { 100 | if(y) { 101 | if(z) { 102 | } 103 | } 104 | }`, 105 | errors: 2, 106 | }, 107 | ], 108 | }); 109 | -------------------------------------------------------------------------------- /tests/rules/no-collection-size-mischeck.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import * as path from "path"; 21 | import { RuleTester } from "eslint"; 22 | import rule = require("../../src/rules/no-collection-size-mischeck"); 23 | 24 | const ruleTesterJs = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); 25 | 26 | ruleTesterJs.run("Collection sizes and array length comparisons should make sense", rule, { 27 | valid: [ 28 | { 29 | code: ` 30 | if (collections.length < 1) {} 31 | if (collections.length > 0) {} 32 | if (collections.length <= 1) {} 33 | if (collections.length >= 1) {} 34 | if (collections.length < 50) {} 35 | if (collections.length < 5 + 0){} 36 | if (collections.size() >= 0) {} 37 | `, 38 | }, 39 | ], 40 | invalid: [ 41 | { 42 | code: `if (collection.size < 0) {}`, 43 | errors: [ 44 | { 45 | message: `Fix this expression; size of "collection" is always greater or equal to zero.`, 46 | line: 1, 47 | endLine: 1, 48 | column: 5, 49 | endColumn: 24, 50 | }, 51 | ], 52 | }, 53 | { 54 | code: `if (collection.length < 0) {}`, 55 | errors: 1, 56 | }, 57 | { 58 | code: `if (collection.length >= 0) {}`, 59 | errors: 1, 60 | }, 61 | ], 62 | }); 63 | 64 | const ruleTesterTs = new RuleTester({ 65 | parser: require.resolve("@typescript-eslint/parser"), 66 | parserOptions: { 67 | ecmaVersion: 2018, 68 | project: path.resolve(`${__dirname}/../resources/tsconfig.json`), 69 | }, 70 | }); 71 | 72 | const filename = path.resolve(`${__dirname}/../resources/file.ts`); 73 | 74 | ruleTesterTs.run("Collection sizes and array length comparisons should make sense", rule, { 75 | valid: [ 76 | { 77 | code: ` 78 | const arr = []; 79 | if (arr.length < 1) {} 80 | if (arr.length > 0) {} 81 | if (arr.length <= 1) {} 82 | if (arr.length >= 1) {} 83 | if (arr.length < 50) {} 84 | if (arr.length < 5 + 0) {} 85 | `, 86 | filename, 87 | }, 88 | { 89 | code: ` 90 | const obj = {length: -4, size: -5, foobar: 42}; 91 | if (obj.foobar >= 0) {} 92 | if (obj.size >= 0) {} 93 | if (obj.length >= 0) {} 94 | if (obj.length < 0) {} 95 | if (obj.length < 53) {} 96 | if (obj.length > 0) {} 97 | `, 98 | filename, 99 | }, 100 | ], 101 | invalid: [ 102 | { 103 | code: ` 104 | const arr = []; 105 | if (arr.length < 0) {} 106 | `, 107 | filename, 108 | errors: [ 109 | { 110 | message: `Fix this expression; length of "arr" is always greater or equal to zero.`, 111 | line: 3, 112 | endLine: 3, 113 | column: 11, 114 | endColumn: 25, 115 | }, 116 | ], 117 | }, 118 | { 119 | code: ` 120 | const arr = []; 121 | if (arr.length >= 0) {} 122 | `, 123 | filename, 124 | errors: 1, 125 | }, 126 | { 127 | code: ` 128 | const arr = new Array(); 129 | if (arr.length >= 0) {} 130 | `, 131 | filename, 132 | errors: 1, 133 | }, 134 | { 135 | code: ` 136 | const set = new Set(); 137 | if (set.length < 0) {} 138 | `, 139 | filename, 140 | errors: 1, 141 | }, 142 | { 143 | code: ` 144 | const map = new Map(); 145 | if (map.length < 0) {} 146 | `, 147 | filename, 148 | errors: 1, 149 | }, 150 | { 151 | code: ` 152 | const set = new WeakSet(); 153 | if (set.length < 0) {} 154 | `, 155 | filename, 156 | errors: 1, 157 | }, 158 | { 159 | code: ` 160 | const map = new WeakMap(); 161 | if (map.length < 0) {} 162 | `, 163 | filename, 164 | errors: 1, 165 | }, 166 | ], 167 | }); 168 | -------------------------------------------------------------------------------- /tests/rules/no-duplicate-string.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-duplicate-string"); 22 | 23 | const ruleTester = new RuleTester({ 24 | parserOptions: { ecmaVersion: 2015, sourceType: "module", ecmaFeatures: { jsx: true } }, 25 | }); 26 | 27 | ruleTester.run("no-duplicate-string", rule, { 28 | valid: [ 29 | { 30 | code: ` 31 | console.log("some message"); 32 | console.log("some message"); 33 | console.log('some message');`, 34 | options: [4], 35 | }, 36 | { 37 | code: ` // too small 38 | console.log("a&b"); 39 | console.log("a&b"); 40 | console.log("a&b");`, 41 | }, 42 | { 43 | code: ` // too small when trimmed 44 | // trimming allows to not raise issue for flowtype whitespaces 45 | // which are created as literals for some reason 46 | console.log(" a "); 47 | console.log(" a "); 48 | console.log(" a ");`, 49 | }, 50 | { 51 | code: ` // numbers 52 | console.log(12345.67890); 53 | console.log(12345.67890); 54 | console.log(12345.67890);`, 55 | }, 56 | { 57 | code: ` 58 | console.log("only 2 times"); 59 | console.log("only 2 times");`, 60 | }, 61 | { 62 | code: `// no separators 63 | console.log("stringstring"); 64 | console.log("stringstring"); 65 | console.log("stringstring"); 66 | console.log("stringstring");`, 67 | }, 68 | { 69 | code: `// ImportDeclaration 70 | import defaultExport1 from "module-name-long"; 71 | import defaultExport2 from "module-name-long"; 72 | import defaultExport3 from "module-name-long"; 73 | `, 74 | }, 75 | { 76 | code: ` // ImportDeclaration 77 | import * as name1 from "module-name-long"; 78 | import * as name2 from "module-name-long"; 79 | import * as name3 from "module-name-long"; 80 | `, 81 | }, 82 | { 83 | code: ` // ImportDeclaration 84 | import "module-name-long"; 85 | import "module-name-long"; 86 | import "module-name-long"; 87 | `, 88 | }, 89 | { 90 | code: `// ExportAllDeclaration 91 | export * from "module-name-long"; 92 | export * from "module-name-long"; 93 | export * from "module-name-long"; 94 | `, 95 | }, 96 | { 97 | code: `// CallExpression 'require' 98 | const a = require("module-name-long").a; 99 | const b = require("module-name-long").b; 100 | const c = require("module-name-long").c; 101 | `, 102 | }, 103 | { 104 | code: `// ExportNamedDeclaration 105 | export { a } from "module-name-long"; 106 | export { b } from "module-name-long"; 107 | export { c } from "module-name-long"; 108 | `, 109 | }, 110 | { 111 | code: ` // JSXAttribute 112 | ; 113 | ; 114 | ; 115 | ; 116 | `, 117 | }, 118 | { 119 | code: ` 120 | console.log(\`some message\`); 121 | console.log('some message'); 122 | console.log("some message");`, 123 | }, 124 | { 125 | code: ` 126 | const obj1 = { 127 | "some property": 1 128 | }; 129 | const obj2 = { 130 | "some property": 1 131 | }; 132 | const obj3 = { 133 | "some property": 1 134 | };`, 135 | }, 136 | { 137 | code: ` 138 | 'use strict'; 139 | 'use strict'; 140 | 'use strict'; 141 | `, 142 | }, 143 | ], 144 | invalid: [ 145 | { 146 | code: ` 147 | console.log("some message"); 148 | console.log("some message"); 149 | console.log('some message');`, 150 | errors: [ 151 | { 152 | message: "Define a constant instead of duplicating this literal 3 times.", 153 | column: 17, 154 | endColumn: 31, 155 | }, 156 | ], 157 | }, 158 | { 159 | code: ` 160 | ; 161 | ; 162 | ; 163 | let x = "some-string", y = "some-string", z = "some-string"; 164 | `, 165 | errors: [ 166 | { 167 | message: "Define a constant instead of duplicating this literal 3 times.", 168 | line: 5, 169 | }, 170 | ], 171 | }, 172 | { 173 | code: ` 174 | console.log("some message"); 175 | console.log('some message');`, 176 | errors: 1, 177 | options: [2], 178 | }, 179 | { 180 | code: ` 181 | const obj1 = { 182 | key: "some message" 183 | }; 184 | const obj2 = { 185 | key: "some message" 186 | }; 187 | const obj3 = { 188 | key: "some message" 189 | };`, 190 | errors: 1, 191 | }, 192 | ], 193 | }); 194 | -------------------------------------------------------------------------------- /tests/rules/no-element-overwrite.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-element-overwrite"); 22 | 23 | const ruleTester = new RuleTester(); 24 | 25 | ruleTester.run("no-element-overwrite", rule, { 26 | valid: [ 27 | { 28 | code: ` 29 | fruits[1] = "banana"; 30 | fruits[2] = "apple";`, 31 | }, 32 | { 33 | code: ` 34 | fruits[1] = "banana"; 35 | vegerables[1] = "tomato";`, 36 | }, 37 | { 38 | code: ` 39 | fruits[1] = "banana"; 40 | console.log("Hello"); 41 | fruits[1] = "apple"; // FN`, 42 | }, 43 | { 44 | code: ` 45 | fruits[1] = "banana"; 46 | foo(fruits); 47 | fruits[1] = "apple";`, 48 | }, 49 | { 50 | code: ` 51 | fruits[1] = "banana"; 52 | if (cond) { 53 | fruits[1] = "apple"; 54 | }`, 55 | }, 56 | { 57 | code: ` 58 | fruits[2] = "orange"; 59 | fruits[2] = fruits[2] + ";";`, 60 | }, 61 | { 62 | code: ` 63 | this.fruits[2] = "orange"; 64 | this.fruits[2] = foo(this.fruits) + ";";`, 65 | }, 66 | { 67 | code: ` 68 | this.fruits[2] = "orange"; 69 | this.fruits[2] = foo(this.bar, this.fruits);`, 70 | }, 71 | { 72 | code: ` 73 | function anotherCollection() { 74 | var x = [1,], y = [1, ]; 75 | x[1] = 3; 76 | y[1] = x[1]; 77 | x[1] = 43; // Compliant 78 | }`, 79 | }, 80 | { 81 | code: ` 82 | function indexChanges() { 83 | var nums = []; 84 | var i = 1; 85 | nums[i++] = 42; 86 | nums[i++] = 43; 87 | i += 1; 88 | nums[i] = 2; 89 | i += 1; 90 | nums[i] = 2; 91 | }`, 92 | }, 93 | ], 94 | invalid: [ 95 | { 96 | code: ` 97 | fruits[1] = "banana"; 98 | fruits[1] = "apple";`, 99 | errors: [ 100 | { 101 | message: `Verify this is the index that was intended; "1" was already set on line 2.`, 102 | line: 3, 103 | column: 7, 104 | endColumn: 26, 105 | }, 106 | ], 107 | }, 108 | { 109 | code: ` 110 | fruits[1] = "banana"; 111 | //^^^^^^^^^^^^^^^^^^^^> 112 | fruits[1] = "apple"; 113 | //^^^^^^^^^^^^^^^^^^^`, 114 | options: ["radar-runtime"], 115 | errors: [ 116 | JSON.stringify({ 117 | secondaryLocations: [{ line: 2, column: 6, endLine: 2, endColumn: 26, message: "Original value" }], 118 | message: `Verify this is the index that was intended; "1" was already set on line 2.`, 119 | }), 120 | ], 121 | }, 122 | { 123 | code: ` 124 | fruits[1] = "banana"; 125 | fruits[2] = "orange"; 126 | fruits[1] = "apple";`, 127 | errors: [{ message: `Verify this is the index that was intended; "1" was already set on line 2.` }], 128 | }, 129 | { 130 | code: ` 131 | this.fruits[1] = "banana"; 132 | this.fruits[1] = "apple";`, 133 | errors: [{ message: `Verify this is the index that was intended; "1" was already set on line 2.` }], 134 | }, 135 | { 136 | code: ` 137 | this.fruits[1] = "banana"; 138 | this.fruits[1] = foo(this.bar);`, 139 | errors: [{ message: `Verify this is the index that was intended; "1" was already set on line 2.` }], 140 | }, 141 | { 142 | code: ` 143 | for (var i = 0; i < 10; i++) { 144 | fruits[i] = "melon"; 145 | fruits[i] = "pear"; 146 | fruits[i++] = "another"; 147 | }`, 148 | errors: [{ message: `Verify this is the index that was intended; "i" was already set on line 3.` }], 149 | }, 150 | { 151 | code: ` 152 | myMap.set("key", 1); 153 | myMap.set("key", 2); 154 | myMap.clear(); 155 | myMap.set("key", 1);`, 156 | errors: [{ message: `Verify this is the index that was intended; "key" was already set on line 2.` }], 157 | }, 158 | { 159 | code: ` 160 | mySet.add(1); 161 | mySet.add(2); 162 | mySet.add(3); 163 | mySet.add(2); 164 | mySet.clear(); 165 | mySet.add(2);`, 166 | errors: [{ message: `Verify this is the index that was intended; "2" was already set on line 3.` }], 167 | }, 168 | { 169 | code: ` 170 | function switchTest(kind) { 171 | var result = []; 172 | switch (kind) { 173 | case 1: 174 | result[1] = 1; 175 | result[1] = 2; 176 | break; 177 | case 2: 178 | result[2] = 1; 179 | result[2] = 2; 180 | break; 181 | } 182 | }`, 183 | errors: [ 184 | { message: `Verify this is the index that was intended; "1" was already set on line 6.` }, 185 | { message: `Verify this is the index that was intended; "2" was already set on line 10.` }, 186 | ], 187 | }, 188 | { 189 | code: ` 190 | fruits[''] = "banana"; 191 | fruits[''] = "apple";`, 192 | errors: [{ message: `Verify this is the index that was intended; "" was already set on line 2.` }], 193 | }, 194 | ], 195 | }); 196 | -------------------------------------------------------------------------------- /tests/rules/no-identical-conditions.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-identical-conditions"); 22 | 23 | const ruleTester = new RuleTester(); 24 | 25 | const message = (line: number) => `This branch duplicates the one on line ${line}`; 26 | 27 | ruleTester.run("no-identical-conditions", rule, { 28 | valid: [ 29 | { 30 | code: "if (a) {} else if (b) {}", 31 | }, 32 | { 33 | code: "if (a) {} else {}", 34 | }, 35 | ], 36 | invalid: [ 37 | { 38 | code: ` 39 | if (a) {} 40 | else if (a) {} 41 | `, 42 | errors: [{ message: message(2), line: 3, column: 18, endColumn: 19 }], 43 | }, 44 | { 45 | code: ` 46 | if (a) {} 47 | // ^> 48 | else if (a) {} 49 | // ^ 50 | `, 51 | options: ["radar-runtime"], 52 | errors: [ 53 | JSON.stringify({ 54 | secondaryLocations: [ 55 | { 56 | line: 2, 57 | column: 12, 58 | endLine: 2, 59 | endColumn: 13, 60 | message: "Original", 61 | }, 62 | ], 63 | message: message(2), 64 | }), 65 | ], 66 | }, 67 | { 68 | code: ` 69 | if (b) {} 70 | else if (a) {} 71 | else if (a) {} 72 | `, 73 | errors: [{ message: message(3), line: 4, column: 18, endColumn: 19 }], 74 | }, 75 | { 76 | code: ` 77 | if (a) {} 78 | else if (b) {} 79 | else if (a) {} 80 | `, 81 | errors: [{ message: message(2), line: 4, column: 18, endColumn: 19 }], 82 | }, 83 | ], 84 | }); 85 | -------------------------------------------------------------------------------- /tests/rules/no-identical-expressions.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-identical-expressions"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); 24 | 25 | ruleTester.run("no-identical-expressions", rule, { 26 | valid: [ 27 | { code: `1 << 1;` }, 28 | { code: `foo(), foo();` }, 29 | { code: `if (Foo instanceof Foo) { }` }, 30 | { code: `name === "any" || name === "string" || name === "number" || name === "boolean" || name === "never"` }, 31 | { code: `a != a;` }, 32 | { code: `a === a;` }, 33 | { code: `a !== a;` }, 34 | 35 | { code: `node.text === "eval" || node.text === "arguments";` }, 36 | { code: `nodeText === '"use strict"' || nodeText === "'use strict'";` }, 37 | { code: `name.charCodeAt(0) === CharacterCodes._ && name.charCodeAt(1) === CharacterCodes._;` }, 38 | { code: `if (+a !== +b) { }` }, 39 | { code: "first(`const`) || first(`var`);" }, 40 | // eslint-disable-next-line no-template-curly-in-string 41 | { code: "window[`${prefix}CancelAnimationFrame`] || window[`${prefix}CancelRequestAnimationFrame`];" }, 42 | { code: "" }, 43 | // eslint-disable-next-line no-useless-escape 44 | { code: `dirPath.match(/localhost:\d+/) || dirPath.match(/localhost:\d+\s/);` }, 45 | { code: `a == b || a == c;` }, 46 | { code: `a == b;` }, 47 | ], 48 | invalid: [ 49 | { 50 | code: "a == b && a == b", 51 | errors: [ 52 | { 53 | message: 'Correct one of the identical sub-expressions on both sides of operator "&&"', 54 | column: 1, 55 | endColumn: 17, 56 | }, 57 | ], 58 | }, 59 | { 60 | code: "a == b || a == b", 61 | errors: 1, 62 | }, 63 | { 64 | code: `a == b || a == b 65 | // ^^^^^^> ^^^^^^`, 66 | options: ["radar-runtime"], 67 | errors: [ 68 | { 69 | message: JSON.stringify({ 70 | secondaryLocations: [ 71 | { 72 | line: 1, 73 | column: 0, 74 | endLine: 1, 75 | endColumn: 6, 76 | message: "", 77 | }, 78 | ], 79 | message: `Correct one of the identical sub-expressions on both sides of operator "||"`, 80 | }), 81 | line: 1, 82 | endLine: 1, 83 | column: 11, 84 | endColumn: 17, 85 | }, 86 | ], 87 | }, 88 | { 89 | code: "a > a", 90 | errors: 1, 91 | }, 92 | { 93 | code: "a >= a", 94 | errors: 1, 95 | }, 96 | { 97 | code: "a < a", 98 | errors: 1, 99 | }, 100 | { 101 | code: "a <= a", 102 | errors: 1, 103 | }, 104 | { 105 | code: "5 / 5", 106 | errors: 1, 107 | }, 108 | { 109 | code: "5 - 5", 110 | errors: 1, 111 | }, 112 | { 113 | code: "a << a", 114 | errors: 1, 115 | }, 116 | { 117 | code: "a << a", 118 | errors: 1, 119 | }, 120 | { 121 | code: "a >> a", 122 | errors: 1, 123 | }, 124 | { 125 | code: "1 >> 1", 126 | errors: 1, 127 | }, 128 | { 129 | code: "5 << 5", 130 | errors: 1, 131 | }, 132 | { 133 | code: "obj.foo() == obj.foo()", 134 | errors: 1, 135 | }, 136 | { 137 | code: "foo(/*comment*/() => doSomething()) === foo(() => doSomething())", 138 | errors: 1, 139 | }, 140 | { 141 | code: "(a == b) == (a == b)", 142 | errors: 1, 143 | }, 144 | { 145 | code: "if (+a !== +a);", 146 | errors: 1, 147 | }, 148 | ], 149 | }); 150 | -------------------------------------------------------------------------------- /tests/rules/no-inverted-boolean-check.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-inverted-boolean-check"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); 24 | 25 | ruleTester.run("no-inverted-boolean-check", rule, { 26 | valid: [ 27 | { 28 | code: `if (!x) {}`, 29 | }, 30 | { 31 | code: `if (x == 1) {}`, 32 | }, 33 | { 34 | code: `if (!(x + 1)) {}`, 35 | }, 36 | { 37 | code: `if (+(x == 1)) {}`, 38 | }, 39 | { 40 | code: `!x ? 2 : 3`, 41 | }, 42 | ], 43 | invalid: [ 44 | // `==` => `!=` 45 | { 46 | code: `if (!(x == 1)) {}`, 47 | errors: [ 48 | { 49 | message: message("!="), 50 | line: 1, 51 | endLine: 1, 52 | column: 5, 53 | endColumn: 14, 54 | }, 55 | ], 56 | output: `if (x != 1) {}`, 57 | }, 58 | // `!=` => `==` 59 | { 60 | code: `if (!(x != 1)) {}`, 61 | errors: [message("==")], 62 | output: `if (x == 1) {}`, 63 | }, 64 | // `===` => `!==` 65 | { 66 | code: `if (!(x === 1)) {}`, 67 | errors: [message("!==")], 68 | output: `if (x !== 1) {}`, 69 | }, 70 | // `!==` => `===` 71 | { 72 | code: `if (!(x !== 1)) {}`, 73 | errors: [message("===")], 74 | output: `if (x === 1) {}`, 75 | }, 76 | // `>` => `<=` 77 | { 78 | code: `if (!(x > 1)) {}`, 79 | errors: [message("<=")], 80 | output: `if (x <= 1) {}`, 81 | }, 82 | // `<` => `>=` 83 | { 84 | code: `if (!(x < 1)) {}`, 85 | errors: [message(">=")], 86 | output: `if (x >= 1) {}`, 87 | }, 88 | // `>=` => `<` 89 | { 90 | code: `if (!(x >= 1)) {}`, 91 | errors: [message("<")], 92 | output: `if (x < 1) {}`, 93 | }, 94 | // `<=` => `>` 95 | { 96 | code: `if (!(x <= 1)) {}`, 97 | errors: [message(">")], 98 | output: `if (x > 1) {}`, 99 | }, 100 | // ternary operator 101 | { 102 | code: `!(x != 1) ? 1 : 2`, 103 | errors: [message("==")], 104 | output: `x == 1 ? 1 : 2`, 105 | }, 106 | // not conditional 107 | { 108 | code: `foo(!(x === 1))`, 109 | errors: [message("!==")], 110 | output: `foo(x !== 1)`, 111 | }, 112 | { 113 | code: `let foo = !(x <= 4)`, 114 | errors: [message(">")], 115 | output: `let foo = x > 4`, 116 | }, 117 | ], 118 | }); 119 | 120 | function message(oppositeOperator: string) { 121 | return `Use the opposite operator (${oppositeOperator}) instead.`; 122 | } 123 | -------------------------------------------------------------------------------- /tests/rules/no-redundant-boolean.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-redundant-boolean"); 22 | 23 | const ruleTester = new RuleTester(); 24 | 25 | ruleTester.run("no-redundant-boolean", rule, { 26 | valid: [ 27 | { code: "a === false;" }, 28 | { code: "a === true;" }, 29 | { code: "a !== false;" }, 30 | { code: "a !== true;" }, 31 | { code: "a == foo(true);" }, 32 | { code: "true < 0;" }, 33 | { code: "~true;" }, 34 | { code: "!foo;" }, 35 | { code: "if (foo(mayBeSomething || false)) {}" }, 36 | { code: "x ? y || false : z" }, 37 | ], 38 | invalid: [ 39 | { 40 | code: "if (x == true) {}", 41 | errors: [{ message: "Remove the unnecessary boolean literal.", column: 10, endColumn: 14 }], 42 | }, 43 | { code: "if (x == false) {}", errors: 1 }, 44 | { code: "if (x || false) {}", errors: 1 }, 45 | { code: "if (x && false) {}", errors: 1 }, 46 | 47 | { code: "x || false ? 1 : 2", errors: 1 }, 48 | 49 | { code: "fn(!false)", errors: 1 }, 50 | 51 | { code: "a == true == b;", errors: 1 }, 52 | { code: "a == b == false;", errors: 1 }, 53 | { code: "a == (true == b) == b;", errors: 1 }, 54 | 55 | { code: "!(true);", errors: 1 }, 56 | { code: "a == (false);", errors: 1 }, 57 | 58 | { code: "true && a;", errors: 1 }, 59 | ], 60 | }); 61 | -------------------------------------------------------------------------------- /tests/rules/no-redundant-jump.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-redundant-jump"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); 24 | 25 | function invalid(code: string) { 26 | const line = code.split("\n").findIndex((str) => str.includes("// Noncompliant")) + 1; 27 | return { 28 | code, 29 | errors: [ 30 | { 31 | message: "Remove this redundant jump.", 32 | line, 33 | endLine: line, 34 | }, 35 | ], 36 | }; 37 | } 38 | 39 | ruleTester.run("Jump statements should not be redundant", rule, { 40 | invalid: [ 41 | invalid( 42 | `while (x == 1) { 43 | console.log("x == 1"); 44 | continue; // Noncompliant 45 | }`, 46 | ), 47 | invalid( 48 | `function redundantJump(condition1, condition2) { 49 | while (condition1) { 50 | if (condition2) { 51 | console.log("Hello"); 52 | continue; // Noncompliant 53 | } else { 54 | console.log("else"); 55 | } 56 | } 57 | }`, 58 | ), 59 | invalid( 60 | `function redundantJump(condition1, condition2) { 61 | while (condition1) { 62 | if (condition2) { 63 | console.log("then"); 64 | } else { 65 | console.log("else"); 66 | continue; // Noncompliant 67 | } 68 | } 69 | }`, 70 | ), 71 | invalid( 72 | `function redundantJump() { 73 | for (let i = 0; i < 10; i++) { 74 | console.log("Hello"); 75 | continue; // Noncompliant 76 | } 77 | }`, 78 | ), 79 | invalid( 80 | `function redundantJump(b) { 81 | if (b) { 82 | console.log("b"); 83 | return; // Noncompliant 84 | } 85 | }`, 86 | ), 87 | invalid( 88 | `function redundantJump(x) { 89 | console.log("x == 1"); 90 | return; // Noncompliant 91 | }`, 92 | ), 93 | invalid( 94 | `const redundantJump = (x) => { 95 | console.log("x == 1"); 96 | return; // Noncompliant 97 | }`, 98 | ), 99 | ], 100 | valid: [ 101 | { 102 | code: ` 103 | function return_with_value() { 104 | foo(); 105 | return 42; 106 | } 107 | 108 | function switch_statements(x) { 109 | switch (x) { 110 | case 0: 111 | foo(); 112 | break; 113 | default: 114 | } 115 | foo(); 116 | switch (x) { 117 | case 0: 118 | foo(); 119 | return; 120 | case 1: 121 | bar(); 122 | return; 123 | } 124 | } 125 | 126 | function loops_with_label() { 127 | for (let i = 0; i < 10; i++) { 128 | inner: for (let j = 0; j < 10; j++) { 129 | continue inner; 130 | } 131 | } 132 | } 133 | 134 | function compliant(b) { 135 | for (let i = 0; i < 10; i++) { 136 | break; 137 | } 138 | if (b) { 139 | console.log("b"); 140 | return; 141 | } 142 | console.log("useful"); 143 | } 144 | 145 | while (x == 1) { 146 | continue; // Ok, we ignore when 1 statement 147 | } 148 | 149 | function bar() { 150 | return; // Ok, we ignore when 1 statement 151 | } 152 | `, 153 | }, 154 | ], 155 | }); 156 | -------------------------------------------------------------------------------- /tests/rules/no-same-line-conditional.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-same-line-conditional"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018, sourceType: "module" } }); 24 | 25 | ruleTester.run("Conditionals should start on new lines", rule, { 26 | valid: [ 27 | { 28 | code: ` 29 | if (cond1) 30 | if (cond2) { 31 | if (cond3) { 32 | } 33 | }`, 34 | }, 35 | { 36 | code: ` 37 | if (cond1) { 38 | } else if (cond2) { 39 | } else if (cond3) { 40 | }`, 41 | }, 42 | { 43 | code: ` 44 | if (cond1) { 45 | } 46 | if (cond2) { 47 | } else if (cond3) { 48 | }`, 49 | }, 50 | { 51 | code: ` 52 | if (cond1) 53 | doSomething(); 54 | if (cond2) { 55 | }`, 56 | }, 57 | { 58 | code: `foo(); if (cond) bar();`, 59 | }, 60 | { 61 | // OK if everything is on one line 62 | code: `if (cond1) foo(); if (cond2) bar();`, 63 | }, 64 | { 65 | code: ` 66 | function myFunc() { 67 | if (cond1) { 68 | } else if (cond2) { 69 | } else if (cond3) { 70 | } 71 | }`, 72 | }, 73 | { 74 | code: ` 75 | switch(x) { 76 | case 1: 77 | if (cond1) { 78 | } else if (cond2) { 79 | } else if (cond3) { 80 | } 81 | break; 82 | default: 83 | if (cond1) { 84 | } else if (cond2) { 85 | } else if (cond3) { 86 | } 87 | break; 88 | }`, 89 | }, 90 | ], 91 | invalid: [ 92 | { 93 | code: ` 94 | if (cond1) { 95 | } if (cond2) { 96 | }`, 97 | errors: [ 98 | { 99 | message: 100 | '{"message":"Move this \\"if\\" to a new line or add the missing \\"else\\".","secondaryLocations":[{"column":6,"line":3,"endColumn":7,"endLine":3}]}', 101 | line: 3, 102 | endLine: 3, 103 | column: 9, 104 | endColumn: 11, 105 | }, 106 | ], 107 | }, 108 | { 109 | code: ` 110 | switch(x) { 111 | case 1: 112 | if (cond1) { 113 | } else if (cond2) { 114 | } if (cond3) { 115 | } 116 | break; 117 | default: 118 | if (cond1) { 119 | } if (cond2) { 120 | } else if (cond3) { 121 | } 122 | break; 123 | }`, 124 | errors: [ 125 | { 126 | message: 127 | '{"message":"Move this \\"if\\" to a new line or add the missing \\"else\\".","secondaryLocations":[{"column":10,"line":6,"endColumn":11,"endLine":6}]}', 128 | line: 6, 129 | endLine: 6, 130 | column: 13, 131 | endColumn: 15, 132 | }, 133 | { 134 | message: 135 | '{"message":"Move this \\"if\\" to a new line or add the missing \\"else\\".","secondaryLocations":[{"column":10,"line":11,"endColumn":11,"endLine":11}]}', 136 | line: 11, 137 | endLine: 11, 138 | column: 13, 139 | endColumn: 15, 140 | }, 141 | ], 142 | }, 143 | { 144 | code: ` 145 | if (cond1) { 146 | } else if (cond2) { 147 | } if (cond3) { 148 | }`, 149 | errors: [ 150 | { 151 | message: 152 | '{"message":"Move this \\"if\\" to a new line or add the missing \\"else\\".","secondaryLocations":[{"column":6,"line":4,"endColumn":7,"endLine":4}]}', 153 | }, 154 | ], 155 | }, 156 | { 157 | code: ` 158 | if (cond1) 159 | if (cond2) { 160 | if (cond3) { 161 | } if (cond4) { 162 | } 163 | }`, 164 | errors: [ 165 | { 166 | message: 167 | '{"message":"Move this \\"if\\" to a new line or add the missing \\"else\\".","secondaryLocations":[{"column":10,"line":5,"endColumn":11,"endLine":5}]}', 168 | }, 169 | ], 170 | }, 171 | { 172 | code: ` 173 | function myFunc() { 174 | if (cond1) { 175 | } else if (cond2) { 176 | } if (cond3) { 177 | } 178 | }`, 179 | errors: [ 180 | { 181 | message: 182 | '{"message":"Move this \\"if\\" to a new line or add the missing \\"else\\".","secondaryLocations":[{"column":8,"line":5,"endColumn":9,"endLine":5}]}', 183 | }, 184 | ], 185 | }, 186 | ], 187 | }); 188 | -------------------------------------------------------------------------------- /tests/rules/no-small-switch.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-small-switch"); 22 | 23 | const ruleTester = new RuleTester(); 24 | 25 | ruleTester.run("no-small-switch", rule, { 26 | valid: [ 27 | { code: "switch (a) { case 1: case 2: break; default: doSomething(); break; }" }, 28 | { code: "switch (a) { case 1: break; default: doSomething(); break; case 2: }" }, 29 | { code: "switch (a) { case 1: break; case 2: }" }, 30 | ], 31 | invalid: [ 32 | { 33 | code: "switch (a) { case 1: doSomething(); break; default: doSomething(); }", 34 | errors: [{ message: '"switch" statements should have at least 3 "case" clauses', column: 1, endColumn: 7 }], 35 | }, 36 | { 37 | code: "switch (a) { case 1: break; }", 38 | errors: 1, 39 | }, 40 | { 41 | code: "switch (a) {}", 42 | errors: 1, 43 | }, 44 | ], 45 | }); 46 | -------------------------------------------------------------------------------- /tests/rules/no-use-of-empty-return-value.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-use-of-empty-return-value"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2017 } }); 24 | 25 | const FUNCTION_NO_RETURN = "function noReturn() { }\n "; 26 | 27 | ruleTester.run("no-use-of-empty-return-value", rule, { 28 | valid: [ 29 | { code: "function withReturn() { return 1; } console.log(withReturn());" }, 30 | { code: "let x = () => {}; if (cond) {x = () => 1} let y = x();" }, 31 | { code: "var x = function x() { return 42 }; y = x();" }, 32 | { code: FUNCTION_NO_RETURN + "noReturn();" }, 33 | { code: FUNCTION_NO_RETURN + "async function foo() { await noReturn(); }" }, 34 | { code: FUNCTION_NO_RETURN + "function foo() { return noReturn(); }" }, 35 | { code: FUNCTION_NO_RETURN + "(noReturn());" }, 36 | { code: FUNCTION_NO_RETURN + "let arrowFunc = p => noReturn();" }, 37 | { code: FUNCTION_NO_RETURN + "let arrowFunc = p => (noReturn());" }, 38 | { code: FUNCTION_NO_RETURN + "cond ? noReturn() : somethingElse();" }, 39 | { code: FUNCTION_NO_RETURN + "boolVar && noReturn();" }, 40 | { code: FUNCTION_NO_RETURN + "boolVar || noReturn();" }, 41 | { code: "function noReturn() { return; }; noReturn();" }, 42 | { code: "function withReturn() { return 42; }; x = noReturn();" }, 43 | { code: "(function(){}());" }, 44 | { code: "!function(){}();" }, 45 | { code: "class A { methodNoReturn() {}\n foo() { console.log(this.methodNoReturn()); } }" }, // FN 46 | { code: "var arrowImplicitReturn = (a) => a*2; x = arrowImplicitReturn(1);" }, 47 | { code: "var arrowReturnsPromise = async () => { var x = () => {return 1} }; x = arrowReturnsPromise();" }, 48 | { code: "async function statementReturnsPromise() { var x = () => {return 1} }\n x = statementReturnsPromise();" }, 49 | { code: "function* noReturn() { yield 1; } noReturn().next();" }, 50 | { code: "function* noReturn() { yield 1; } noReturn();" }, 51 | ], 52 | invalid: [ 53 | invalidPrefixWithFunction("console.log(noReturn());"), 54 | invalidPrefixWithFunction("x = noReturn();"), 55 | invalidPrefixWithFunction("noReturn() ? foo() : bar();"), 56 | invalidPrefixWithFunction("noReturn().foo();"), 57 | invalidPrefixWithFunction("let x = noReturn();"), 58 | invalidPrefixWithFunction("for (var x in noReturn()) { }"), 59 | invalidPrefixWithFunction("for (var x of noReturn()) { }"), 60 | invalidPrefixWithFunction("noReturn() && doSomething();"), 61 | invalid("var noReturn = function () { 1; }; console.log(noReturn());"), 62 | invalid("var noReturn = () => { 42;}; console.log(noReturn());"), 63 | invalid("function noReturn() { return; }; console.log(noReturn());"), 64 | invalid("var noReturn = function () { let x = () => { return 42 }; }; console.log(noReturn());"), 65 | invalid("var funcExpr = function noReturn () { 1; console.log(noReturn()); };"), 66 | invalid("var noReturn = () => { var x = () => {return 1} }; x = noReturn();"), 67 | ], 68 | }); 69 | 70 | function invalidPrefixWithFunction( 71 | code: string, 72 | functionName: string = "noReturn", 73 | ): { code: string; errors: RuleTester.TestCaseError[] } { 74 | return { 75 | code: "function noReturn() { 1;} " + code, 76 | errors: [ 77 | { message: `Remove this use of the output from "${functionName}"; "${functionName}" doesn't return anything.` }, 78 | ], 79 | }; 80 | } 81 | 82 | function invalid(code: string): { code: string; errors: RuleTester.TestCaseError[] } { 83 | return { 84 | code, 85 | errors: [{ message: `Remove this use of the output from "noReturn"; "noReturn" doesn't return anything.` }], 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /tests/rules/no-useless-catch.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/no-useless-catch"); 22 | 23 | const ruleTester = new RuleTester({ 24 | parserOptions: { ecmaVersion: 10 }, 25 | }); 26 | 27 | ruleTester.run("no-useless-catch", rule, { 28 | valid: [ 29 | { code: `try {} catch (e) {}` }, 30 | { code: `try {} catch { throw "Error"; }` }, 31 | { 32 | code: `try {} catch (e) { 33 | foo(); 34 | throw e; 35 | }`, 36 | }, 37 | { 38 | code: `try {} catch({ message }) { 39 | throw { message }; // OK, not useless, we might ignore other properties of exception 40 | }`, 41 | }, 42 | { 43 | code: `try {} catch (e) { 44 | if (x) { 45 | throw e; 46 | } 47 | }`, 48 | }, 49 | { 50 | code: `try {} catch(e) { throw "foo"; }`, 51 | }, 52 | { 53 | code: `try {} catch(e) { throw new Error("improve error message"); }`, 54 | }, 55 | ], 56 | invalid: [ 57 | { 58 | code: `try {} catch (e) { throw e; }`, 59 | errors: [ 60 | { 61 | message: "Add logic to this catch clause or eliminate it and rethrow the exception automatically.", 62 | line: 1, 63 | endLine: 1, 64 | column: 8, 65 | endColumn: 13, 66 | }, 67 | ], 68 | }, 69 | { 70 | code: `try {} catch(e) { 71 | // some comment 72 | throw e; 73 | }`, 74 | errors: 1, 75 | }, 76 | { 77 | code: `try { 78 | doSomething(); 79 | } catch(e) { 80 | throw e; 81 | } finally { 82 | // ... 83 | }`, 84 | errors: 1, 85 | }, 86 | ], 87 | }); 88 | -------------------------------------------------------------------------------- /tests/rules/prefer-object-literal.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/prefer-object-literal"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); 24 | 25 | ruleTester.run("prefer-literal", rule, { 26 | valid: [ 27 | { 28 | code: `var x = {a: 2}`, 29 | }, 30 | { 31 | code: ` 32 | function Foo(a) { 33 | this.a = a; 34 | }; 35 | var x = new Foo(2);`, 36 | }, 37 | { 38 | code: ` 39 | var x = {a: 2}; 40 | y = "foo";`, 41 | }, 42 | // FN 43 | { 44 | code: ` 45 | var x; 46 | x = {}; 47 | x.a = 2`, 48 | }, 49 | // FN 50 | { 51 | code: `var x = {a: 2}; doSomething(); x.b = 3;`, 52 | }, 53 | { 54 | code: ` 55 | function foo() { 56 | var x = {a: 2}; 57 | doSomething(); 58 | }`, 59 | }, 60 | { 61 | code: `var x = {}; x["a"] = 2;`, 62 | }, 63 | // No issue on multiline expressions, may be done for readibility 64 | { 65 | code: ` 66 | var x = {}; 67 | x.foo = function () { 68 | doSomething(); 69 | } 70 | var y = {}; 71 | y.prop = { 72 | a: 1, 73 | b: 2 74 | }`, 75 | }, 76 | // OK, report only when empty object 77 | { 78 | code: `var x = {a: 2}; x.b = 5;`, 79 | }, 80 | ], 81 | invalid: [ 82 | { 83 | code: `var x = {}; x.a = 2;`, 84 | errors: [ 85 | { 86 | message: 87 | "Declare one or more properties of this object inside of the object literal syntax instead of using separate statements.", 88 | line: 1, 89 | endLine: 1, 90 | column: 5, 91 | endColumn: 11, 92 | }, 93 | ], 94 | }, 95 | { 96 | code: ` 97 | var x = {}, 98 | y = "hello"; 99 | x.a = 2;`, 100 | errors: [ 101 | { 102 | message: 103 | "Declare one or more properties of this object inside of the object literal syntax instead of using separate statements.", 104 | line: 2, 105 | endLine: 2, 106 | column: 13, 107 | endColumn: 19, 108 | }, 109 | ], 110 | }, 111 | { 112 | code: `var x = {}; x.a = 2; x.b = 3`, 113 | errors: 1, 114 | }, 115 | { 116 | code: `let x = {}; x.a = 2;`, 117 | errors: 1, 118 | }, 119 | { 120 | code: `const x = {}; x.a = 2;`, 121 | errors: 1, 122 | }, 123 | { 124 | code: `{ var x = {}; x.a = 2; }`, 125 | errors: 1, 126 | }, 127 | { 128 | code: `if (a) { var x = {}; x.a = 2; }`, 129 | errors: 1, 130 | }, 131 | { 132 | code: `function foo() { 133 | var x = {}; 134 | x.a = 2; 135 | }`, 136 | errors: 1, 137 | }, 138 | ], 139 | }); 140 | -------------------------------------------------------------------------------- /tests/rules/prefer-single-boolean-return.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/prefer-single-boolean-return"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); 24 | 25 | ruleTester.run("prefer-single-boolean-return", rule, { 26 | valid: [ 27 | { 28 | code: ` 29 | function foo() { 30 | if (something) { 31 | return true; 32 | } else if (something) { 33 | return false; 34 | } else { 35 | return true; 36 | } 37 | } 38 | `, 39 | }, 40 | { 41 | code: ` 42 | function foo() { 43 | if (something) { 44 | return x; 45 | } else { 46 | return false; 47 | } 48 | } 49 | `, 50 | }, 51 | { 52 | code: ` 53 | function foo(y) { 54 | if (something) { 55 | return true; 56 | } else { 57 | return foo; 58 | } 59 | } 60 | `, 61 | }, 62 | { 63 | code: ` 64 | function foo() { 65 | if (something) { 66 | doSomething(); 67 | } else { 68 | return true; 69 | } 70 | } 71 | `, 72 | }, 73 | { 74 | code: ` 75 | function foo() { 76 | if (something) { 77 | doSomething(); 78 | return true; 79 | } else { 80 | return false; 81 | } 82 | } 83 | `, 84 | }, 85 | { 86 | code: ` 87 | function foo() { 88 | if (something) { 89 | return; 90 | } else { 91 | return true; 92 | } 93 | } 94 | `, 95 | }, 96 | { 97 | code: ` 98 | function foo() { 99 | if (something) { 100 | return true; 101 | } 102 | } 103 | `, 104 | }, 105 | { 106 | code: ` 107 | function foo() { 108 | if (something) { 109 | return foo(true); 110 | } else { 111 | return foo(false); 112 | } 113 | } 114 | `, 115 | }, 116 | { 117 | code: ` 118 | function foo() { 119 | if (something) { 120 | var x; 121 | } else { 122 | return false; 123 | } 124 | } 125 | `, 126 | }, 127 | { 128 | code: ` 129 | function foo() { 130 | if (something) { 131 | function f() {} 132 | return false; 133 | } else { 134 | return true; 135 | } 136 | } 137 | `, 138 | }, 139 | ], 140 | invalid: [ 141 | { 142 | code: ` 143 | function foo() { 144 | if (something) { 145 | return true; 146 | } else { 147 | return false; 148 | } 149 | 150 | if (something) { 151 | return false; 152 | } else { 153 | return true; 154 | } 155 | 156 | if (something) return true; 157 | else return false; 158 | 159 | if (something) { 160 | return true; 161 | } else { 162 | return true; 163 | } 164 | } 165 | `, 166 | errors: [ 167 | { 168 | message: "Replace this if-then-else statement by a single return statement.", 169 | line: 3, 170 | column: 11, 171 | endLine: 7, 172 | endColumn: 12, 173 | }, 174 | { line: 9, column: 11, endLine: 13, endColumn: 12 }, 175 | { line: 15, column: 11, endLine: 16, endColumn: 29 }, 176 | { line: 18, column: 11, endLine: 22, endColumn: 12 }, 177 | ], 178 | }, 179 | ], 180 | }); 181 | -------------------------------------------------------------------------------- /tests/rules/prefer-while.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * eslint-plugin-sonarjs 3 | * Copyright (C) 2018 SonarSource SA 4 | * mailto:info AT sonarsource DOT com 5 | * 6 | * This program is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation, 9 | * version 3. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | import { RuleTester } from "eslint"; 21 | import rule = require("../../src/rules/prefer-while"); 22 | 23 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); 24 | const message = 'Replace this "for" loop with a "while" loop.'; 25 | 26 | ruleTester.run("prefer-while", rule, { 27 | valid: [ 28 | { code: "for(var i = 0; condition;) { }" }, 29 | { code: "for(var i = 0; condition; i++) { }" }, 30 | { code: "for(var i = 0;; i++) { }" }, 31 | { code: "for (i; condition; ) { }" }, 32 | { code: "for ( ; i < length; i++ ) { }" }, 33 | { code: "while (i < length) { }" }, 34 | { code: "for (a in b) { }" }, 35 | { code: "for (a of b) { }" }, 36 | ], 37 | invalid: [ 38 | { 39 | code: "for(;condition;) {}", 40 | errors: [{ message, line: 1, column: 1, endColumn: 4 }], 41 | output: "while (condition) {}", 42 | }, 43 | { 44 | code: "for (;condition; ) foo();", 45 | errors: [{ message }], 46 | output: "while (condition) foo();", 47 | }, 48 | { 49 | code: ` 50 | for(;i < 10;) 51 | doSomething();`, 52 | errors: [{ message }], 53 | output: ` 54 | while (i < 10) 55 | doSomething();`, 56 | }, 57 | { 58 | code: "for(;;) {}", 59 | errors: [{ message }], 60 | output: "for(;;) {}", // no fix 61 | }, 62 | ], 63 | }); 64 | -------------------------------------------------------------------------------- /tsconfig-src.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "outDir": "lib", 6 | "sourceMap": true 7 | }, 8 | "include": ["src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "lib": ["es2017"], 6 | "strict": true, 7 | "noImplicitAny": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "noEmit": true, 11 | "allowSyntheticDefaultImports": true 12 | }, 13 | "include": ["ruling/**/*.ts", "src/**/*.ts", "tests/**/*.ts"], 14 | "exclude": ["ruling/javascript-test-sources"] 15 | } 16 | --------------------------------------------------------------------------------