├── .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 |
--------------------------------------------------------------------------------