diff --git a/.eslint-doc-generatorrc.js b/.eslint-doc-generatorrc.js index 0f1f832fab..f4a896b273 100644 --- a/.eslint-doc-generatorrc.js +++ b/.eslint-doc-generatorrc.js @@ -15,7 +15,7 @@ const config = { 'hasSuggestions', 'requiresTypeChecking', ], - urlConfigs: 'https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs', + urlConfigs: 'https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs', }; module.exports = config; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a4b8dcc525..1856cb48a0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,7 @@ --- name: Bug report about: A rule isn't working as it should? +labels: bug --- diff --git a/.github/ISSUE_TEMPLATE/rule_change.yml b/.github/ISSUE_TEMPLATE/rule_change.yml new file mode 100644 index 0000000000..fc9fab8f2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/rule_change.yml @@ -0,0 +1,55 @@ +name: Rule change +description: A rule could be improved or extended? +title: "`rule-name`: " +labels: + - change request + - evaluating +body: + - type: markdown + attributes: + value: | + Before opening a new issue: + - Look for existing [open or closed rule proposals](https://github.com/sindresorhus/eslint-plugin-unicorn/issues?q=is:issue+label%25enhancement) + - [Report a bug](https://github.com/sindresorhus/eslint-plugin-unicorn/issues/new?assignees=&labels=&projects=&template=bug_report.md) instead if it's a false positive + - type: textarea + validations: + required: true + attributes: + label: Description + description: Explain the improvement you would like to see. + - type: textarea + validations: + required: true + attributes: + label: Fail + description: Specify examples of code that should be detected. + value: | + ```js + var replace = 'me'; + ``` + + ```js + function foo() { + var replace = 'me'; + return replace; + } + ``` + - type: textarea + validations: + required: true + attributes: + label: Pass + description: Specify examples of code that would be accepted in its place. + value: | + ```js + const replace = 'me'; + ``` + + ```js + function foo() { + return 'me'; + } + ``` + - type: textarea + attributes: + label: Additional Info diff --git a/.github/ISSUE_TEMPLATE/rule_proposal.yml b/.github/ISSUE_TEMPLATE/rule_proposal.yml index 1a067bfbd2..386190b28d 100644 --- a/.github/ISSUE_TEMPLATE/rule_proposal.yml +++ b/.github/ISSUE_TEMPLATE/rule_proposal.yml @@ -49,6 +49,12 @@ body: return 'me'; } ``` + - type: input + validations: + required: true + attributes: + label: Proposed rule name + placeholder: no-invalid-rule-name - type: textarea attributes: label: Additional Info diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000000..f107622125 --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,4 @@ +github: [sindresorhus, fisker] +open_collective: sindresorhus +buy_me_a_coffee: sindresorhus +custom: https://sindresorhus.com/donate diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b34af09a40..bd0cf3e0a5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,13 +16,14 @@ jobs: matrix: node-version: - 20 - - 16 + - 18 os: - ubuntu-latest - windows-latest - include: - - os: ubuntu-latest - node-version: 18 + # Even numbers of node-version + # include: + # - os: ubuntu-latest + # node-version: 18 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -51,10 +52,11 @@ jobs: env: AVA_FORCE_CI: not-ci - run: git diff --exit-code - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: fail_ci_if_error: true files: coverage/lcov.info + token: ${{ secrets.CODECOV_TOKEN }} run-rules-on-codebase: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 258deecada..d8a56aa0cb 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -9,13 +9,10 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - - run: | - npm install - npm link - npm link eslint-plugin-unicorn - - uses: AriPerkkio/eslint-remote-tester-run-action@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - run: npm install + - uses: AriPerkkio/eslint-remote-tester-run-action@v5 with: issue-title: "Results of weekly scheduled smoke test" - eslint-remote-tester-config: test/smoke/eslint-remote-tester.config.js + eslint-remote-tester-config: test/smoke/eslint-remote-tester.config.mjs diff --git a/.npmpackagejsonlintrc.json b/.npmpackagejsonlintrc.json index fc1c26c606..2e91278ad3 100644 --- a/.npmpackagejsonlintrc.json +++ b/.npmpackagejsonlintrc.json @@ -7,15 +7,16 @@ "prefer-alphabetical-optionalDependencies": "error", "prefer-alphabetical-bundledDependencies": "error", "prefer-alphabetical-scripts": "error", - "prefer-caret-version-dependencies": [ + "prefer-caret-version-dependencies": "error", + "prefer-caret-version-devDependencies": [ "error", { "exceptions": [ - "eslint-plugin-unicorn" + "eslint-plugin-internal-rules", + "eslint-doc-generator" ] } ], - "prefer-caret-version-devDependencies": "error", "prefer-scripts": [ "error", [ diff --git a/configs/all.js b/configs/all.js deleted file mode 100644 index 743845a378..0000000000 --- a/configs/all.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; -const recommended = require('./recommended.js'); - -module.exports = Object.fromEntries(Object.entries(recommended).map( - ([ruleId, severity]) => [ruleId, ruleId.startsWith('unicorn/') ? 'error' : severity], -)); diff --git a/configs/recommended.js b/configs/recommended.js deleted file mode 100644 index 2da3c5e960..0000000000 --- a/configs/recommended.js +++ /dev/null @@ -1,120 +0,0 @@ -'use strict'; -module.exports = { - 'unicorn/better-regex': 'error', - 'unicorn/catch-error-name': 'error', - 'unicorn/consistent-destructuring': 'off', - 'unicorn/consistent-function-scoping': 'error', - 'unicorn/custom-error-definition': 'off', - 'unicorn/empty-brace-spaces': 'error', - 'unicorn/error-message': 'error', - 'unicorn/escape-case': 'error', - 'unicorn/expiring-todo-comments': 'error', - 'unicorn/explicit-length-check': 'error', - 'unicorn/filename-case': 'error', - 'unicorn/import-style': 'error', - 'unicorn/new-for-builtins': 'error', - 'unicorn/no-abusive-eslint-disable': 'error', - 'unicorn/no-anonymous-default-export': 'error', - 'unicorn/no-array-callback-reference': 'error', - 'unicorn/no-array-for-each': 'error', - 'unicorn/no-array-method-this-argument': 'error', - 'unicorn/no-array-push-push': 'error', - 'unicorn/no-array-reduce': 'error', - 'unicorn/no-await-expression-member': 'error', - 'unicorn/no-await-in-promise-methods': 'error', - 'unicorn/no-console-spaces': 'error', - 'unicorn/no-document-cookie': 'error', - 'unicorn/no-empty-file': 'error', - 'unicorn/no-for-loop': 'error', - 'unicorn/no-hex-escape': 'error', - 'unicorn/no-instanceof-array': 'error', - 'unicorn/no-invalid-remove-event-listener': 'error', - 'unicorn/no-keyword-prefix': 'off', - 'unicorn/no-lonely-if': 'error', - 'no-negated-condition': 'off', - 'unicorn/no-negated-condition': 'error', - 'no-nested-ternary': 'off', - 'unicorn/no-nested-ternary': 'error', - 'unicorn/no-new-array': 'error', - 'unicorn/no-new-buffer': 'error', - 'unicorn/no-null': 'error', - 'unicorn/no-object-as-default-parameter': 'error', - 'unicorn/no-process-exit': 'error', - 'unicorn/no-single-promise-in-promise-methods': 'error', - 'unicorn/no-static-only-class': 'error', - 'unicorn/no-thenable': 'error', - 'unicorn/no-this-assignment': 'error', - 'unicorn/no-typeof-undefined': 'error', - 'unicorn/no-unnecessary-await': 'error', - 'unicorn/no-unnecessary-polyfills': 'error', - 'unicorn/no-unreadable-array-destructuring': 'error', - 'unicorn/no-unreadable-iife': 'error', - 'unicorn/no-unused-properties': 'off', - 'unicorn/no-useless-fallback-in-spread': 'error', - 'unicorn/no-useless-length-check': 'error', - 'unicorn/no-useless-promise-resolve-reject': 'error', - 'unicorn/no-useless-spread': 'error', - 'unicorn/no-useless-switch-case': 'error', - 'unicorn/no-useless-undefined': 'error', - 'unicorn/no-zero-fractions': 'error', - 'unicorn/number-literal-case': 'error', - 'unicorn/numeric-separators-style': 'error', - 'unicorn/prefer-add-event-listener': 'error', - 'unicorn/prefer-array-find': 'error', - 'unicorn/prefer-array-flat': 'error', - 'unicorn/prefer-array-flat-map': 'error', - 'unicorn/prefer-array-index-of': 'error', - 'unicorn/prefer-array-some': 'error', - 'unicorn/prefer-at': 'error', - 'unicorn/prefer-blob-reading-methods': 'error', - 'unicorn/prefer-code-point': 'error', - 'unicorn/prefer-date-now': 'error', - 'unicorn/prefer-default-parameters': 'error', - 'unicorn/prefer-dom-node-append': 'error', - 'unicorn/prefer-dom-node-dataset': 'error', - 'unicorn/prefer-dom-node-remove': 'error', - 'unicorn/prefer-dom-node-text-content': 'error', - 'unicorn/prefer-event-target': 'error', - 'unicorn/prefer-export-from': 'error', - 'unicorn/prefer-includes': 'error', - 'unicorn/prefer-json-parse-buffer': 'off', - 'unicorn/prefer-keyboard-event-key': 'error', - 'unicorn/prefer-logical-operator-over-ternary': 'error', - 'unicorn/prefer-math-trunc': 'error', - 'unicorn/prefer-modern-dom-apis': 'error', - 'unicorn/prefer-modern-math-apis': 'error', - 'unicorn/prefer-module': 'error', - 'unicorn/prefer-native-coercion-functions': 'error', - 'unicorn/prefer-negative-index': 'error', - 'unicorn/prefer-node-protocol': 'error', - 'unicorn/prefer-number-properties': 'error', - 'unicorn/prefer-object-from-entries': 'error', - 'unicorn/prefer-optional-catch-binding': 'error', - 'unicorn/prefer-prototype-methods': 'error', - 'unicorn/prefer-query-selector': 'error', - 'unicorn/prefer-reflect-apply': 'error', - 'unicorn/prefer-regexp-test': 'error', - 'unicorn/prefer-set-has': 'error', - 'unicorn/prefer-set-size': 'error', - 'unicorn/prefer-spread': 'error', - 'unicorn/prefer-string-replace-all': 'error', - 'unicorn/prefer-string-slice': 'error', - 'unicorn/prefer-string-starts-ends-with': 'error', - 'unicorn/prefer-string-trim-start-end': 'error', - 'unicorn/prefer-switch': 'error', - 'unicorn/prefer-ternary': 'error', - 'unicorn/prefer-top-level-await': 'error', - 'unicorn/prefer-type-error': 'error', - 'unicorn/prevent-abbreviations': 'error', - 'unicorn/relative-url-style': 'error', - 'unicorn/require-array-join-separator': 'error', - 'unicorn/require-number-to-fixed-digits-argument': 'error', - // Turned off because we can't distinguish `widow.postMessage` and `{Worker,MessagePort,Client,BroadcastChannel}#postMessage()` - // See #1396 - 'unicorn/require-post-message-target-origin': 'off', - 'unicorn/string-content': 'off', - 'unicorn/switch-case-braces': 'error', - 'unicorn/template-indent': 'error', - 'unicorn/text-encoding-identifier-case': 'error', - 'unicorn/throw-new-error': 'error', -}; diff --git a/docs/rules/better-regex.md b/docs/rules/better-regex.md index f64c3b0fe4..c2baeb27fa 100644 --- a/docs/rules/better-regex.md +++ b/docs/rules/better-regex.md @@ -1,6 +1,6 @@ # Improve regexes by making them shorter, consistent, and safer -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/catch-error-name.md b/docs/rules/catch-error-name.md index 9348f21f3f..5fe97e3143 100644 --- a/docs/rules/catch-error-name.md +++ b/docs/rules/catch-error-name.md @@ -1,6 +1,6 @@ # Enforce a specific parameter name in catch clauses -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/consistent-destructuring.md b/docs/rules/consistent-destructuring.md index bdc6cd708e..1a4ef0d51b 100644 --- a/docs/rules/consistent-destructuring.md +++ b/docs/rules/consistent-destructuring.md @@ -1,6 +1,6 @@ # Use destructured variables over properties -🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/consistent-empty-array-spread.md b/docs/rules/consistent-empty-array-spread.md new file mode 100644 index 0000000000..6317cf507f --- /dev/null +++ b/docs/rules/consistent-empty-array-spread.md @@ -0,0 +1,42 @@ +# Prefer consistent types when spreading a ternary in an array literal + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + + +When spreading a ternary in an array, we can use both `[]` and `''` as fallbacks, but it's better to have consistent types in both branches. + +## Fail + +```js +const array = [ + a, + ...(foo ? [b, c] : ''), +]; +``` + +```js +const array = [ + a, + ...(foo ? 'bc' : []), +]; +``` + +## Pass + +```js +const array = [ + a, + ...(foo ? [b, c] : []), +]; +``` + +```js +const array = [ + a, + ...(foo ? 'bc' : ''), +]; +``` diff --git a/docs/rules/consistent-function-scoping.md b/docs/rules/consistent-function-scoping.md index f03ddc35b9..2f4d122493 100644 --- a/docs/rules/consistent-function-scoping.md +++ b/docs/rules/consistent-function-scoping.md @@ -1,6 +1,6 @@ # Move function definitions to the highest possible scope -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/custom-error-definition.md b/docs/rules/custom-error-definition.md index 62590cc229..73b6d5e960 100644 --- a/docs/rules/custom-error-definition.md +++ b/docs/rules/custom-error-definition.md @@ -1,6 +1,6 @@ # Enforce correct `Error` subclassing -🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/empty-brace-spaces.md b/docs/rules/empty-brace-spaces.md index e6df45dac5..f0b275cf91 100644 --- a/docs/rules/empty-brace-spaces.md +++ b/docs/rules/empty-brace-spaces.md @@ -1,6 +1,6 @@ # Enforce no spaces between braces -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/error-message.md b/docs/rules/error-message.md index c1bfca442d..0a195c33a5 100644 --- a/docs/rules/error-message.md +++ b/docs/rules/error-message.md @@ -1,6 +1,6 @@ # Enforce passing a `message` value when creating a built-in error -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/escape-case.md b/docs/rules/escape-case.md index e302e66af7..18b9782ebe 100644 --- a/docs/rules/escape-case.md +++ b/docs/rules/escape-case.md @@ -1,6 +1,6 @@ # Require escape sequences to use uppercase values -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/expiring-todo-comments.md b/docs/rules/expiring-todo-comments.md index f4bc55e614..5135bc5b5f 100644 --- a/docs/rules/expiring-todo-comments.md +++ b/docs/rules/expiring-todo-comments.md @@ -1,6 +1,6 @@ # Add expiration conditions to TODO comments -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/explicit-length-check.md b/docs/rules/explicit-length-check.md index decb245355..58d0a804c0 100644 --- a/docs/rules/explicit-length-check.md +++ b/docs/rules/explicit-length-check.md @@ -1,6 +1,6 @@ # Enforce explicitly comparing the `length` or `size` property of a value -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/filename-case.md b/docs/rules/filename-case.md index c4f19c3267..197167b892 100644 --- a/docs/rules/filename-case.md +++ b/docs/rules/filename-case.md @@ -1,6 +1,6 @@ # Enforce a case style for filenames -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/import-style.md b/docs/rules/import-style.md index d60d310ef4..805725f088 100644 --- a/docs/rules/import-style.md +++ b/docs/rules/import-style.md @@ -1,6 +1,6 @@ # Enforce specific import styles per module -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/new-for-builtins.md b/docs/rules/new-for-builtins.md index 871e87d8fe..ad1453c774 100644 --- a/docs/rules/new-for-builtins.md +++ b/docs/rules/new-for-builtins.md @@ -1,6 +1,6 @@ # Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-abusive-eslint-disable.md b/docs/rules/no-abusive-eslint-disable.md index ce0b800a98..ea0eb3a89c 100644 --- a/docs/rules/no-abusive-eslint-disable.md +++ b/docs/rules/no-abusive-eslint-disable.md @@ -1,6 +1,6 @@ # Enforce specifying rules to disable in `eslint-disable` comments -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-anonymous-default-export.md b/docs/rules/no-anonymous-default-export.md index 8525b946c5..e8675c0c6a 100644 --- a/docs/rules/no-anonymous-default-export.md +++ b/docs/rules/no-anonymous-default-export.md @@ -1,6 +1,6 @@ # Disallow anonymous functions and classes as the default export -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-array-callback-reference.md b/docs/rules/no-array-callback-reference.md index 035c698225..e1cb6f386f 100644 --- a/docs/rules/no-array-callback-reference.md +++ b/docs/rules/no-array-callback-reference.md @@ -1,6 +1,6 @@ # Prevent passing a function reference directly to iterator methods -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-array-for-each.md b/docs/rules/no-array-for-each.md index e6b01f10a8..015440cca0 100644 --- a/docs/rules/no-array-for-each.md +++ b/docs/rules/no-array-for-each.md @@ -1,6 +1,6 @@ # Prefer `for…of` over the `forEach` method -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-array-method-this-argument.md b/docs/rules/no-array-method-this-argument.md index 8dea99564a..f16fa56a12 100644 --- a/docs/rules/no-array-method-this-argument.md +++ b/docs/rules/no-array-method-this-argument.md @@ -1,6 +1,6 @@ # Disallow using the `this` argument in array methods -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-array-push-push.md b/docs/rules/no-array-push-push.md index 914fe9f87e..09a65cbf1a 100644 --- a/docs/rules/no-array-push-push.md +++ b/docs/rules/no-array-push-push.md @@ -1,6 +1,6 @@ # Enforce combining multiple `Array#push()` into one call -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-array-reduce.md b/docs/rules/no-array-reduce.md index fc8af7ea1f..858f6b838a 100644 --- a/docs/rules/no-array-reduce.md +++ b/docs/rules/no-array-reduce.md @@ -1,6 +1,6 @@ # Disallow `Array#reduce()` and `Array#reduceRight()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-await-expression-member.md b/docs/rules/no-await-expression-member.md index 3bd13b5109..632655faf0 100644 --- a/docs/rules/no-await-expression-member.md +++ b/docs/rules/no-await-expression-member.md @@ -1,6 +1,6 @@ # Disallow member access from await expression -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-await-in-promise-methods.md b/docs/rules/no-await-in-promise-methods.md index e42e16d55d..3f440bdac3 100644 --- a/docs/rules/no-await-in-promise-methods.md +++ b/docs/rules/no-await-in-promise-methods.md @@ -1,6 +1,6 @@ # Disallow using `await` in `Promise` method parameters -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-console-spaces.md b/docs/rules/no-console-spaces.md index d205924a90..56b913386d 100644 --- a/docs/rules/no-console-spaces.md +++ b/docs/rules/no-console-spaces.md @@ -1,6 +1,6 @@ # Do not use leading/trailing space between `console.log` parameters -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-document-cookie.md b/docs/rules/no-document-cookie.md index a49eb60a4f..3dc54a1eba 100644 --- a/docs/rules/no-document-cookie.md +++ b/docs/rules/no-document-cookie.md @@ -1,6 +1,6 @@ # Do not use `document.cookie` directly -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-empty-file.md b/docs/rules/no-empty-file.md index 72244e26d4..16fa4ac302 100644 --- a/docs/rules/no-empty-file.md +++ b/docs/rules/no-empty-file.md @@ -1,6 +1,6 @@ # Disallow empty files -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-for-loop.md b/docs/rules/no-for-loop.md index 4b6d3d5cac..5affa0208c 100644 --- a/docs/rules/no-for-loop.md +++ b/docs/rules/no-for-loop.md @@ -1,6 +1,6 @@ # Do not use a `for` loop that can be replaced with a `for-of` loop -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-hex-escape.md b/docs/rules/no-hex-escape.md index af5c3052c4..ac90330966 100644 --- a/docs/rules/no-hex-escape.md +++ b/docs/rules/no-hex-escape.md @@ -1,6 +1,6 @@ # Enforce the use of Unicode escapes instead of hexadecimal escapes -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-instanceof-array.md b/docs/rules/no-instanceof-array.md index 28078b1398..05bf59dc8d 100644 --- a/docs/rules/no-instanceof-array.md +++ b/docs/rules/no-instanceof-array.md @@ -1,6 +1,6 @@ # Require `Array.isArray()` instead of `instanceof Array` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-invalid-fetch-options.md b/docs/rules/no-invalid-fetch-options.md new file mode 100644 index 0000000000..ea304d00d7 --- /dev/null +++ b/docs/rules/no-invalid-fetch-options.md @@ -0,0 +1,44 @@ +# Disallow invalid options in `fetch()` and `new Request()` + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + + + + +[`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) throws a `TypeError` when the method is `GET` or `HEAD` and a body is provided. + +## Fail + +```js +const response = await fetch('/', {body: 'foo=bar'}); +``` + +```js +const request = new Request('/', {body: 'foo=bar'}); +``` + +```js +const response = await fetch('/', {method: 'GET', body: 'foo=bar'}); +``` + +```js +const request = new Request('/', {method: 'GET', body: 'foo=bar'}); +``` + +## Pass + +```js +const response = await fetch('/', {method: 'HEAD'}); +``` + +```js +const request = new Request('/', {method: 'HEAD'}); +``` + +```js +const response = await fetch('/', {method: 'POST', body: 'foo=bar'}); +``` + +```js +const request = new Request('/', {method: 'POST', body: 'foo=bar'}); +``` diff --git a/docs/rules/no-invalid-remove-event-listener.md b/docs/rules/no-invalid-remove-event-listener.md index ca4fe78585..06f3a743e4 100644 --- a/docs/rules/no-invalid-remove-event-listener.md +++ b/docs/rules/no-invalid-remove-event-listener.md @@ -1,6 +1,6 @@ # Prevent calling `EventTarget#removeEventListener()` with the result of an expression -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-keyword-prefix.md b/docs/rules/no-keyword-prefix.md index 7398732bbc..18671a5c44 100644 --- a/docs/rules/no-keyword-prefix.md +++ b/docs/rules/no-keyword-prefix.md @@ -1,6 +1,6 @@ # Disallow identifiers starting with `new` or `class` -🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-lonely-if.md b/docs/rules/no-lonely-if.md index 02f62640f4..3c0cf5e894 100644 --- a/docs/rules/no-lonely-if.md +++ b/docs/rules/no-lonely-if.md @@ -1,6 +1,6 @@ # Disallow `if` statements as the only statement in `if` blocks without `else` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-magic-array-flat-depth.md b/docs/rules/no-magic-array-flat-depth.md new file mode 100644 index 0000000000..c90ee32ee1 --- /dev/null +++ b/docs/rules/no-magic-array-flat-depth.md @@ -0,0 +1,40 @@ +# Disallow a magic number as the `depth` argument in `Array#flat(…).` + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + + + + +When calling [`Array#flat(depth)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat), the depth argument should normally be `1` or `Infinity`, otherwise it should be a meaningful variable name or explained with a comment. + +## Fail + +```js +const foo = array.flat(2); +``` + +```js +const foo = array.flat(99); +``` + +## Pass + +```js +const foo = array.flat(); +``` + +```js +const foo = array.flat(Number.POSITIVE_INFINITY); +``` + +```js +const foo = array.flat(Infinity); +``` + +```js +const foo = array.flat(depth); +``` + +```js +const foo = array.flat(/* The depth is always 2 */ 2); +``` diff --git a/docs/rules/no-negated-condition.md b/docs/rules/no-negated-condition.md index 496d6a5644..3c1624218f 100644 --- a/docs/rules/no-negated-condition.md +++ b/docs/rules/no-negated-condition.md @@ -1,6 +1,6 @@ # Disallow negated conditions -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-negation-in-equality-check.md b/docs/rules/no-negation-in-equality-check.md new file mode 100644 index 0000000000..248261ebeb --- /dev/null +++ b/docs/rules/no-negation-in-equality-check.md @@ -0,0 +1,30 @@ +# Disallow negated expression in equality check + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). + + + + +Using a negated expression in equality check is most likely a mistake. + +## Fail + +```js +if (!foo === bar) {} +``` + +```js +if (!foo !== bar) {} +``` + +## Pass + +```js +if (foo !== bar) {} +``` + +```js +if (!(foo === bar)) {} +``` diff --git a/docs/rules/no-nested-ternary.md b/docs/rules/no-nested-ternary.md index d937c7c3d2..b4ecf32363 100644 --- a/docs/rules/no-nested-ternary.md +++ b/docs/rules/no-nested-ternary.md @@ -1,6 +1,6 @@ # Disallow nested ternary expressions -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-new-array.md b/docs/rules/no-new-array.md index 574513309c..c75f3e4c5b 100644 --- a/docs/rules/no-new-array.md +++ b/docs/rules/no-new-array.md @@ -1,6 +1,6 @@ # Disallow `new Array()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-new-buffer.md b/docs/rules/no-new-buffer.md index 982b7338ae..e83f7bf954 100644 --- a/docs/rules/no-new-buffer.md +++ b/docs/rules/no-new-buffer.md @@ -1,6 +1,6 @@ # Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-null.md b/docs/rules/no-null.md index 15f5417894..f739e14c8c 100644 --- a/docs/rules/no-null.md +++ b/docs/rules/no-null.md @@ -1,6 +1,6 @@ # Disallow the use of the `null` literal -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-object-as-default-parameter.md b/docs/rules/no-object-as-default-parameter.md index f052643b5d..1089080520 100644 --- a/docs/rules/no-object-as-default-parameter.md +++ b/docs/rules/no-object-as-default-parameter.md @@ -1,6 +1,6 @@ # Disallow the use of objects as default parameters -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-process-exit.md b/docs/rules/no-process-exit.md index 60e6a14598..9f47af80e1 100644 --- a/docs/rules/no-process-exit.md +++ b/docs/rules/no-process-exit.md @@ -1,6 +1,6 @@ # Disallow `process.exit()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-single-promise-in-promise-methods.md b/docs/rules/no-single-promise-in-promise-methods.md index 50cb47e4f5..1cc12515f0 100644 --- a/docs/rules/no-single-promise-in-promise-methods.md +++ b/docs/rules/no-single-promise-in-promise-methods.md @@ -1,6 +1,6 @@ # Disallow passing single-element arrays to `Promise` methods -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-static-only-class.md b/docs/rules/no-static-only-class.md index d06ad9b41c..9efb5eb9b0 100644 --- a/docs/rules/no-static-only-class.md +++ b/docs/rules/no-static-only-class.md @@ -1,6 +1,6 @@ # Disallow classes that only have static members -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-thenable.md b/docs/rules/no-thenable.md index 93bfb58d8a..bbb2ec84f7 100644 --- a/docs/rules/no-thenable.md +++ b/docs/rules/no-thenable.md @@ -1,6 +1,6 @@ # Disallow `then` property -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-this-assignment.md b/docs/rules/no-this-assignment.md index 26c7bee6d7..3390eaa3ac 100644 --- a/docs/rules/no-this-assignment.md +++ b/docs/rules/no-this-assignment.md @@ -1,6 +1,6 @@ # Disallow assigning `this` to a variable -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-typeof-undefined.md b/docs/rules/no-typeof-undefined.md index f48a5f84c9..b30c2699a6 100644 --- a/docs/rules/no-typeof-undefined.md +++ b/docs/rules/no-typeof-undefined.md @@ -1,6 +1,6 @@ # Disallow comparing `undefined` using `typeof` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-unnecessary-await.md b/docs/rules/no-unnecessary-await.md index e60a98e73e..5417b30161 100644 --- a/docs/rules/no-unnecessary-await.md +++ b/docs/rules/no-unnecessary-await.md @@ -1,6 +1,6 @@ # Disallow awaiting non-promise values -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-unnecessary-polyfills.md b/docs/rules/no-unnecessary-polyfills.md index c888e32a43..5c8483a6e3 100644 --- a/docs/rules/no-unnecessary-polyfills.md +++ b/docs/rules/no-unnecessary-polyfills.md @@ -1,6 +1,6 @@ # Enforce the use of built-in methods instead of unnecessary polyfills -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-unreadable-array-destructuring.md b/docs/rules/no-unreadable-array-destructuring.md index 70ab510e23..a8046117e5 100644 --- a/docs/rules/no-unreadable-array-destructuring.md +++ b/docs/rules/no-unreadable-array-destructuring.md @@ -1,6 +1,6 @@ # Disallow unreadable array destructuring -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-unreadable-iife.md b/docs/rules/no-unreadable-iife.md index 4124c06bd6..9c2eae6620 100644 --- a/docs/rules/no-unreadable-iife.md +++ b/docs/rules/no-unreadable-iife.md @@ -1,6 +1,6 @@ # Disallow unreadable IIFEs -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-unused-properties.md b/docs/rules/no-unused-properties.md index e9dc8835b3..d77cafc2cf 100644 --- a/docs/rules/no-unused-properties.md +++ b/docs/rules/no-unused-properties.md @@ -1,6 +1,6 @@ # Disallow unused object properties -🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/no-useless-fallback-in-spread.md b/docs/rules/no-useless-fallback-in-spread.md index 56a17a0509..ce14f2edfc 100644 --- a/docs/rules/no-useless-fallback-in-spread.md +++ b/docs/rules/no-useless-fallback-in-spread.md @@ -1,6 +1,6 @@ # Disallow useless fallback when spreading in object literals -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-useless-length-check.md b/docs/rules/no-useless-length-check.md index 8b3ace84ab..3fea70973b 100644 --- a/docs/rules/no-useless-length-check.md +++ b/docs/rules/no-useless-length-check.md @@ -1,6 +1,6 @@ # Disallow useless array length check -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-useless-promise-resolve-reject.md b/docs/rules/no-useless-promise-resolve-reject.md index 92e039716e..4d0857c17e 100644 --- a/docs/rules/no-useless-promise-resolve-reject.md +++ b/docs/rules/no-useless-promise-resolve-reject.md @@ -1,6 +1,6 @@ # Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-useless-spread.md b/docs/rules/no-useless-spread.md index 86465f93a6..8626732b7e 100644 --- a/docs/rules/no-useless-spread.md +++ b/docs/rules/no-useless-spread.md @@ -1,6 +1,6 @@ # Disallow unnecessary spread -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-useless-switch-case.md b/docs/rules/no-useless-switch-case.md index a4fe0952e9..eb7a1faf35 100644 --- a/docs/rules/no-useless-switch-case.md +++ b/docs/rules/no-useless-switch-case.md @@ -1,6 +1,6 @@ # Disallow useless case in switch statements -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/no-useless-undefined.md b/docs/rules/no-useless-undefined.md index 9ea6680af8..af4b50fdd1 100644 --- a/docs/rules/no-useless-undefined.md +++ b/docs/rules/no-useless-undefined.md @@ -1,6 +1,6 @@ # Disallow useless `undefined` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/no-zero-fractions.md b/docs/rules/no-zero-fractions.md index 3d3e4deb36..22495ca480 100644 --- a/docs/rules/no-zero-fractions.md +++ b/docs/rules/no-zero-fractions.md @@ -1,6 +1,6 @@ # Disallow number literals with zero fractions or dangling dots -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/number-literal-case.md b/docs/rules/number-literal-case.md index 4a9a22ef9b..dbb092416e 100644 --- a/docs/rules/number-literal-case.md +++ b/docs/rules/number-literal-case.md @@ -1,6 +1,6 @@ # Enforce proper case for numeric literals -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/numeric-separators-style.md b/docs/rules/numeric-separators-style.md index 12353606fa..4af101fad4 100644 --- a/docs/rules/numeric-separators-style.md +++ b/docs/rules/numeric-separators-style.md @@ -1,6 +1,6 @@ # Enforce the style of numeric separators by correctly grouping digits -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-add-event-listener.md b/docs/rules/prefer-add-event-listener.md index 338cad2a2a..1e2c8ad2f0 100644 --- a/docs/rules/prefer-add-event-listener.md +++ b/docs/rules/prefer-add-event-listener.md @@ -1,6 +1,6 @@ # Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-array-find.md b/docs/rules/prefer-array-find.md index a36c362958..4f4b1db54f 100644 --- a/docs/rules/prefer-array-find.md +++ b/docs/rules/prefer-array-find.md @@ -1,6 +1,6 @@ # Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). @@ -17,10 +17,18 @@ This rule is fixable unless default values are used in declaration or assignment const item = array.filter(x => isUnicorn(x))[0]; ``` +```js +const item = array.filter(x => isUnicorn(x)).at(-1); +``` + ```js const item = array.filter(x => isUnicorn(x)).shift(); ``` +```js +const item = array.filter(x => isUnicorn(x)).pop(); +``` + ```js const [item] = array.filter(x => isUnicorn(x)); ``` @@ -50,25 +58,18 @@ Type: `object` ### checkFromLast Type: `boolean`\ -Default: `false` +Default: `true` -Pass `checkFromLast: true` to check cases searching from last. +Pass `checkFromLast: false` to disable check cases searching from last. -#### Fail +#### Pass ```js -// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": true}] +// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": false}] const item = array.filter(x => isUnicorn(x)).at(-1); ``` ```js -// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": true}] +// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": false}] const item = array.filter(x => isUnicorn(x)).pop(); ``` - -#### Pass - -```js -// eslint unicorn/prefer-array-find: ["error", {"checkFromLast": true}] -const item = array.findLast(x => isUnicorn(x)); -``` diff --git a/docs/rules/prefer-array-flat-map.md b/docs/rules/prefer-array-flat-map.md index 0b67390730..512d2070c2 100644 --- a/docs/rules/prefer-array-flat-map.md +++ b/docs/rules/prefer-array-flat-map.md @@ -1,6 +1,6 @@ # Prefer `.flatMap(…)` over `.map(…).flat()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-array-flat.md b/docs/rules/prefer-array-flat.md index e8795440fb..721596d784 100644 --- a/docs/rules/prefer-array-flat.md +++ b/docs/rules/prefer-array-flat.md @@ -1,6 +1,6 @@ # Prefer `Array#flat()` over legacy techniques to flatten arrays -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-array-index-of.md b/docs/rules/prefer-array-index-of.md index 59292b0e30..421615a072 100644 --- a/docs/rules/prefer-array-index-of.md +++ b/docs/rules/prefer-array-index-of.md @@ -1,6 +1,6 @@ # Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-array-some.md b/docs/rules/prefer-array-some.md index a579540eb6..e7b1899483 100644 --- a/docs/rules/prefer-array-some.md +++ b/docs/rules/prefer-array-some.md @@ -1,6 +1,6 @@ -# Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)` +# Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast,findIndex,findLastIndex}(…)` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). @@ -17,7 +17,11 @@ We only check `.filter().length > 0` and `.filter().length !== 0`. These two non - Comparing the result of [`Array#find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) or [`Array#findLast()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLast) with `undefined`. -This rule is fixable for `.filter(…).length` check and has a suggestion for `.{find,findLast}(…)`. +- Using [`Array#findIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) or [`Array#findLastIndex()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findLastIndex) to ensure at least one element in the array passes a given check. + +This rule is fixable for `.filter(…).length` checks and `.{findIndex,findLastIndex}(…)`. + +This rule provides a suggestion for `.{find,findLast}(…)`. ## Fail @@ -44,11 +48,11 @@ const foo = array.find(element => isUnicorn(element)) ? bar : baz; ``` ```js -const hasUnicorn = array.find(element => isUnicorn(element) !== undefined; +const hasUnicorn = array.find(element => isUnicorn(element)) !== undefined; ``` ```js -const hasUnicorn = array.find(element => isUnicorn(element) != null; +const hasUnicorn = array.find(element => isUnicorn(element)) != null; ``` ```js @@ -62,11 +66,19 @@ const foo = array.findLast(element => isUnicorn(element)) ? bar : baz; ``` ```js -const hasUnicorn = array.findLast(element => isUnicorn(element) !== undefined; +const hasUnicorn = array.findLast(element => isUnicorn(element)) !== undefined; +``` + +```js +const hasUnicorn = array.findLast(element => isUnicorn(element)) != null; +``` + +```js +const hasUnicorn = array.findIndex(element => isUnicorn(element)) !== -1; ``` ```js -const hasUnicorn = array.findLast(element => isUnicorn(element) != null; +const hasUnicorn = array.findLastIndex(element => isUnicorn(element)) !== -1; ``` ```vue diff --git a/docs/rules/prefer-at.md b/docs/rules/prefer-at.md index 1720004a83..50079b3777 100644 --- a/docs/rules/prefer-at.md +++ b/docs/rules/prefer-at.md @@ -1,6 +1,6 @@ # Prefer `.at()` method for index access and `String#charAt()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-blob-reading-methods.md b/docs/rules/prefer-blob-reading-methods.md index f2e2e57145..d6b0d8c3fe 100644 --- a/docs/rules/prefer-blob-reading-methods.md +++ b/docs/rules/prefer-blob-reading-methods.md @@ -1,6 +1,6 @@ # Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/prefer-code-point.md b/docs/rules/prefer-code-point.md index 3ca2de6686..a98b06521d 100644 --- a/docs/rules/prefer-code-point.md +++ b/docs/rules/prefer-code-point.md @@ -1,6 +1,6 @@ # Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-date-now.md b/docs/rules/prefer-date-now.md index df232abc0d..ee50fa7ee9 100644 --- a/docs/rules/prefer-date-now.md +++ b/docs/rules/prefer-date-now.md @@ -1,6 +1,6 @@ # Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-default-parameters.md b/docs/rules/prefer-default-parameters.md index e88d7a483f..699e4ea73c 100644 --- a/docs/rules/prefer-default-parameters.md +++ b/docs/rules/prefer-default-parameters.md @@ -1,6 +1,6 @@ # Prefer default parameters over reassignment -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-dom-node-append.md b/docs/rules/prefer-dom-node-append.md index 9ef1a87c7b..9c108537b1 100644 --- a/docs/rules/prefer-dom-node-append.md +++ b/docs/rules/prefer-dom-node-append.md @@ -1,6 +1,6 @@ # Prefer `Node#append()` over `Node#appendChild()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-dom-node-dataset.md b/docs/rules/prefer-dom-node-dataset.md index 5cdcd31919..5901f42834 100644 --- a/docs/rules/prefer-dom-node-dataset.md +++ b/docs/rules/prefer-dom-node-dataset.md @@ -1,6 +1,6 @@ # Prefer using `.dataset` on DOM elements over calling attribute methods -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-dom-node-remove.md b/docs/rules/prefer-dom-node-remove.md index e0373dd47a..1d54903125 100644 --- a/docs/rules/prefer-dom-node-remove.md +++ b/docs/rules/prefer-dom-node-remove.md @@ -1,6 +1,6 @@ # Prefer `childNode.remove()` over `parentNode.removeChild(childNode)` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-dom-node-text-content.md b/docs/rules/prefer-dom-node-text-content.md index 97755e187b..94867e0ffc 100644 --- a/docs/rules/prefer-dom-node-text-content.md +++ b/docs/rules/prefer-dom-node-text-content.md @@ -1,6 +1,6 @@ # Prefer `.textContent` over `.innerText` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-event-target.md b/docs/rules/prefer-event-target.md index 4ae87c9672..723ee61aa8 100644 --- a/docs/rules/prefer-event-target.md +++ b/docs/rules/prefer-event-target.md @@ -1,6 +1,6 @@ # Prefer `EventTarget` over `EventEmitter` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). diff --git a/docs/rules/prefer-export-from.md b/docs/rules/prefer-export-from.md index affbc9e9de..64c2de5c6a 100644 --- a/docs/rules/prefer-export-from.md +++ b/docs/rules/prefer-export-from.md @@ -1,6 +1,6 @@ # Prefer `export…from` when re-exporting -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-includes.md b/docs/rules/prefer-includes.md index 28a0b8eb9c..4c8f48d323 100644 --- a/docs/rules/prefer-includes.md +++ b/docs/rules/prefer-includes.md @@ -1,13 +1,13 @@ -# Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence +# Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). -All built-ins have `.includes()` in addition to `.indexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()`. +All built-ins have `.includes()` in addition to `.indexOf()` and `.lastIndexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()` and `.lastIndexOf()`. [`Array#some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) is intended for more complex needs. If you are just looking for the index where the given item is present, the code can be simplified to use [`Array#includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). This applies to any search with a literal, a variable, or any expression that doesn't have any explicit side effects. However, if the expression you are looking for relies on an item related to the function (its arguments, the function self, etc.), the case is still valid. @@ -16,99 +16,103 @@ This rule is fixable, unless the search expression in `Array#some()` has side ef ## Fail ```js -[].indexOf('foo') !== -1; +array.indexOf('foo') !== -1; ``` ```js -x.indexOf('foo') != -1; +array.indexOf('foo') !== -1; ``` ```js -str.indexOf('foo') > -1; +string.lastIndexOf('foo') !== -1; ``` ```js -'foobar'.indexOf('foo') >= 0; +array.lastIndexOf('foo') !== -1; ``` ```js -x.indexOf('foo') === -1 +foo.indexOf('foo') != -1; ``` ```js -const isFound = foo.some(x => x === 'foo'); +foo.indexOf('foo') >= 0; ``` ```js -const isFound = foo.some(x => 'foo' === x); +foo.indexOf('foo') > -1; ``` ```js -const isFound = foo.some(x => { - return x === 'foo'; -}); +foo.indexOf('foo') === -1 ``` -## Pass +```js +foo.some(x => x === 'foo'); +``` ```js -const str = 'foobar'; +foo.some(x => 'foo' === x); ``` ```js -str.indexOf('foo') !== -n; +foo.some(x => { + return x === 'foo'; +}); ``` +## Pass + ```js -str.indexOf('foo') !== 1; +foo.indexOf('foo') !== -n; ``` ```js -!str.indexOf('foo') === 1; +foo.indexOf('foo') !== 1; ``` ```js -!str.indexOf('foo') === -n; +foo.indexOf('foo') === 1; ``` ```js -str.includes('foo'); +foo.includes('foo'); ``` ```js -[1,2,3].includes(4); +foo.includes(4); ``` ```js -const isFound = foo.includes('foo'); +foo.includes('foo'); ``` ```js -const isFound = foo.some(x => x == undefined); +foo.some(x => x == undefined); ``` ```js -const isFound = foo.some(x => x !== 'foo'); +foo.some(x => x !== 'foo'); ``` ```js -const isFound = foo.some((x, index) => x === index); +foo.some((x, index) => x === index); ``` ```js -const isFound = foo.some(x => (x === 'foo') && isValid()); +foo.some(x => (x === 'foo') && isValid()); ``` ```js -const isFound = foo.some(x => y === 'foo'); +foo.some(x => y === 'foo'); ``` ```js -const isFound = foo.some(x => y.x === 'foo'); +foo.some(x => y.x === 'foo'); ``` ```js -const isFound = foo.some(x => { +foo.some(x => { const bar = getBar(); return x === bar; }); diff --git a/docs/rules/prefer-json-parse-buffer.md b/docs/rules/prefer-json-parse-buffer.md index b25d4fbd13..6478f8bc63 100644 --- a/docs/rules/prefer-json-parse-buffer.md +++ b/docs/rules/prefer-json-parse-buffer.md @@ -1,6 +1,6 @@ # Prefer reading a JSON file as a buffer -🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-keyboard-event-key.md b/docs/rules/prefer-keyboard-event-key.md index 0734033241..3d39b69911 100644 --- a/docs/rules/prefer-keyboard-event-key.md +++ b/docs/rules/prefer-keyboard-event-key.md @@ -1,6 +1,6 @@ # Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-logical-operator-over-ternary.md b/docs/rules/prefer-logical-operator-over-ternary.md index 4658bc5d75..075a4f0db2 100644 --- a/docs/rules/prefer-logical-operator-over-ternary.md +++ b/docs/rules/prefer-logical-operator-over-ternary.md @@ -1,6 +1,6 @@ # Prefer using a logical operator over a ternary -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-math-trunc.md b/docs/rules/prefer-math-trunc.md index ca2b711a0d..228380cace 100644 --- a/docs/rules/prefer-math-trunc.md +++ b/docs/rules/prefer-math-trunc.md @@ -1,6 +1,6 @@ # Enforce the use of `Math.trunc` instead of bitwise operators -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-modern-dom-apis.md b/docs/rules/prefer-modern-dom-apis.md index c48c7e19f1..805edbd6e2 100644 --- a/docs/rules/prefer-modern-dom-apis.md +++ b/docs/rules/prefer-modern-dom-apis.md @@ -1,6 +1,6 @@ # Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-modern-math-apis.md b/docs/rules/prefer-modern-math-apis.md index c25a7e0bd2..8360f9d976 100644 --- a/docs/rules/prefer-modern-math-apis.md +++ b/docs/rules/prefer-modern-math-apis.md @@ -1,6 +1,6 @@ # Prefer modern `Math` APIs over legacy patterns -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-module.md b/docs/rules/prefer-module.md index b08fb6ba36..25445a94c8 100644 --- a/docs/rules/prefer-module.md +++ b/docs/rules/prefer-module.md @@ -1,6 +1,6 @@ # Prefer JavaScript modules (ESM) over CommonJS -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-native-coercion-functions.md b/docs/rules/prefer-native-coercion-functions.md index d1de6fd48c..c24922616c 100644 --- a/docs/rules/prefer-native-coercion-functions.md +++ b/docs/rules/prefer-native-coercion-functions.md @@ -1,6 +1,6 @@ # Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-negative-index.md b/docs/rules/prefer-negative-index.md index 89c4e33dd0..371cefc1c0 100644 --- a/docs/rules/prefer-negative-index.md +++ b/docs/rules/prefer-negative-index.md @@ -1,6 +1,6 @@ # Prefer negative index over `.length - index` when possible -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-node-protocol.md b/docs/rules/prefer-node-protocol.md index bb5350cad1..4a962ff7fb 100644 --- a/docs/rules/prefer-node-protocol.md +++ b/docs/rules/prefer-node-protocol.md @@ -1,6 +1,6 @@ # Prefer using the `node:` protocol when importing Node.js builtin modules -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-number-properties.md b/docs/rules/prefer-number-properties.md index ace82475e1..42c70c0e49 100644 --- a/docs/rules/prefer-number-properties.md +++ b/docs/rules/prefer-number-properties.md @@ -1,6 +1,6 @@ # Prefer `Number` static properties over global ones -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). @@ -39,14 +39,6 @@ const foo = isFinite(10); if (Object.is(foo, NaN)) {} ``` -```js -const isPositiveZero = value => value === 0 && 1 / value === Infinity; -``` - -```js -const isNegativeZero = value => value === 0 && 1 / value === -Infinity; -``` - ```js const {parseInt} = Number; const foo = parseInt('10', 2); @@ -82,6 +74,14 @@ const isPositiveZero = value => value === 0 && 1 / value === Number.POSITIVE_INF const isNegativeZero = value => value === 0 && 1 / value === Number.NEGATIVE_INFINITY; ``` +```js +const isPositiveZero = value => value === 0 && 1 / value === Infinity; +``` + +```js +const isNegativeZero = value => value === 0 && 1 / value === -Infinity; +``` + ## Options Type: `object` @@ -89,9 +89,9 @@ Type: `object` ### checkInfinity Type: `boolean`\ -Default: `true` +Default: `false` -Pass `checkInfinity: false` to disable check on `Infinity`. +Pass `checkInfinity: true` to enable check on `Infinity`. #### Fail @@ -107,6 +107,16 @@ const foo = -Infinity; #### Pass +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkInfinity": true}] +const foo = Number.POSITIVE_INFINITY; +``` + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkInfinity": true}] +const foo = Number.NEGATIVE_INFINITY; +``` + ```js // eslint unicorn/prefer-number-properties: ["error", {"checkInfinity": false}] const foo = Infinity; @@ -116,3 +126,54 @@ const foo = Infinity; // eslint unicorn/prefer-number-properties: ["error", {"checkInfinity": false}] const foo = -Infinity; ``` + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkInfinity": true}] +const isPositiveZero = value => value === 0 && 1 / value === Number.POSITIVE_INFINITY; +``` + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkInfinity": true}] +const isNegativeZero = value => value === 0 && 1 / value === Number.NEGATIVE_INFINITY; +``` + +### checkNaN + +Type: `boolean`\ +Default: `true` + +Pass `checkNaN: false` to disable check on `NaN`. + +#### Fail + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkNaN": true}] +const foo = NaN; +``` + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkNaN": true}] +const foo = -NaN; +``` + +#### Pass + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkNaN": true}] +const foo = Number.NaN; +``` + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkNaN": true}] +const foo = -Number.NaN; +``` + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkNaN": false}] +const foo = NaN; +``` + +```js +// eslint unicorn/prefer-number-properties: ["error", {"checkNaN": false}] +const foo = -NaN; +``` diff --git a/docs/rules/prefer-object-from-entries.md b/docs/rules/prefer-object-from-entries.md index 293591e599..78e5cf177e 100644 --- a/docs/rules/prefer-object-from-entries.md +++ b/docs/rules/prefer-object-from-entries.md @@ -1,6 +1,6 @@ # Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-optional-catch-binding.md b/docs/rules/prefer-optional-catch-binding.md index 19415bb8f3..6c911c6b0c 100644 --- a/docs/rules/prefer-optional-catch-binding.md +++ b/docs/rules/prefer-optional-catch-binding.md @@ -1,6 +1,6 @@ # Prefer omitting the `catch` binding parameter -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-prototype-methods.md b/docs/rules/prefer-prototype-methods.md index 89f8f1af62..1666278a6c 100644 --- a/docs/rules/prefer-prototype-methods.md +++ b/docs/rules/prefer-prototype-methods.md @@ -1,6 +1,6 @@ # Prefer borrowing methods from the prototype instead of the instance -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-query-selector.md b/docs/rules/prefer-query-selector.md index a31f583d81..e8a2e9882e 100644 --- a/docs/rules/prefer-query-selector.md +++ b/docs/rules/prefer-query-selector.md @@ -1,6 +1,6 @@ # Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-reflect-apply.md b/docs/rules/prefer-reflect-apply.md index 54d6f5d243..ca40f93f31 100644 --- a/docs/rules/prefer-reflect-apply.md +++ b/docs/rules/prefer-reflect-apply.md @@ -1,6 +1,6 @@ # Prefer `Reflect.apply()` over `Function#apply()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-regexp-test.md b/docs/rules/prefer-regexp-test.md index ee714ac534..cbb32d6a75 100644 --- a/docs/rules/prefer-regexp-test.md +++ b/docs/rules/prefer-regexp-test.md @@ -1,6 +1,6 @@ # Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-set-has.md b/docs/rules/prefer-set-has.md index f4a5070f13..e0aec4f902 100644 --- a/docs/rules/prefer-set-has.md +++ b/docs/rules/prefer-set-has.md @@ -1,6 +1,6 @@ # Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-set-size.md b/docs/rules/prefer-set-size.md index 86e76b393b..d53c266846 100644 --- a/docs/rules/prefer-set-size.md +++ b/docs/rules/prefer-set-size.md @@ -1,6 +1,6 @@ # Prefer using `Set#size` instead of `Array#length` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-spread.md b/docs/rules/prefer-spread.md index 2f118204b3..8a8e1e8f16 100644 --- a/docs/rules/prefer-spread.md +++ b/docs/rules/prefer-spread.md @@ -1,6 +1,6 @@ # Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split('')` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-string-raw.md b/docs/rules/prefer-string-raw.md new file mode 100644 index 0000000000..c6e1f30daa --- /dev/null +++ b/docs/rules/prefer-string-raw.md @@ -0,0 +1,30 @@ +# Prefer using the `String.raw` tag to avoid escaping `\` + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + + +[`String.raw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw) can be used to avoid escaping `\`. + +## Fail + +```js +const file = "C:\\windows\\style\\path\\to\\file.js"; +``` + +```js +const regexp = new RegExp('foo\\.bar'); +``` + +## Pass + +```js +const file = String.raw`C:\windows\style\path\to\file.js`; +``` + +```js +const regexp = new RegExp(String.raw`foo\.bar`); +``` diff --git a/docs/rules/prefer-string-replace-all.md b/docs/rules/prefer-string-replace-all.md index 001acabfe0..909c6f9691 100644 --- a/docs/rules/prefer-string-replace-all.md +++ b/docs/rules/prefer-string-replace-all.md @@ -1,6 +1,6 @@ # Prefer `String#replaceAll()` over regex searches with the global flag -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-string-slice.md b/docs/rules/prefer-string-slice.md index 66440c2b01..5901a49485 100644 --- a/docs/rules/prefer-string-slice.md +++ b/docs/rules/prefer-string-slice.md @@ -1,6 +1,6 @@ # Prefer `String#slice()` over `String#substr()` and `String#substring()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-string-starts-ends-with.md b/docs/rules/prefer-string-starts-ends-with.md index 2c41311dd9..bba16c0613 100644 --- a/docs/rules/prefer-string-starts-ends-with.md +++ b/docs/rules/prefer-string-starts-ends-with.md @@ -1,6 +1,6 @@ # Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-string-trim-start-end.md b/docs/rules/prefer-string-trim-start-end.md index 5d5df7753c..9c85efa9bc 100644 --- a/docs/rules/prefer-string-trim-start-end.md +++ b/docs/rules/prefer-string-trim-start-end.md @@ -1,6 +1,6 @@ # Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-structured-clone.md b/docs/rules/prefer-structured-clone.md new file mode 100644 index 0000000000..100bbab1a1 --- /dev/null +++ b/docs/rules/prefer-structured-clone.md @@ -0,0 +1,59 @@ +# Prefer using `structuredClone` to create a deep clone + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). + + + + +[`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) is the modern way to create a deep clone of a value. + +## Fail + +```js +const clone = JSON.parse(JSON.stringify(foo)); +``` + +```js +const clone = _.cloneDeep(foo); +``` + +## Pass + +```js +const clone = structuredClone(foo); +``` + +## Options + +Type: `object` + +### functions + +Type: `string[]` + +You can also check custom functions that creates a deep clone. + +`_.cloneDeep()` and `lodash.cloneDeep()` are always checked. + +Example: + +```js +{ + 'unicorn/prefer-structured-clone': [ + 'error', + { + functions: [ + 'cloneDeep', + 'utils.clone' + ] + } + ] +} +``` + +```js +// eslint unicorn/prefer-structured-clone: ["error", {"functions": ["utils.clone"]}] +const clone = utils.clone(foo); // Fails +``` diff --git a/docs/rules/prefer-switch.md b/docs/rules/prefer-switch.md index 9753b4c96e..1d90f8bd08 100644 --- a/docs/rules/prefer-switch.md +++ b/docs/rules/prefer-switch.md @@ -1,6 +1,6 @@ # Prefer `switch` over multiple `else-if` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-ternary.md b/docs/rules/prefer-ternary.md index 6d69da1753..dadc611320 100644 --- a/docs/rules/prefer-ternary.md +++ b/docs/rules/prefer-ternary.md @@ -1,6 +1,6 @@ # Prefer ternary expressions over simple `if-else` statements -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prefer-top-level-await.md b/docs/rules/prefer-top-level-await.md index 83a0c4ca7b..cf34974e79 100644 --- a/docs/rules/prefer-top-level-await.md +++ b/docs/rules/prefer-top-level-await.md @@ -1,6 +1,6 @@ # Prefer top-level await over top-level promises and async function calls -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/prefer-type-error.md b/docs/rules/prefer-type-error.md index 0ed7a3d8d7..0ab23ce712 100644 --- a/docs/rules/prefer-type-error.md +++ b/docs/rules/prefer-type-error.md @@ -1,6 +1,6 @@ # Enforce throwing `TypeError` in type checking conditions -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/prevent-abbreviations.md b/docs/rules/prevent-abbreviations.md index 69f274b523..d511363c8d 100644 --- a/docs/rules/prevent-abbreviations.md +++ b/docs/rules/prevent-abbreviations.md @@ -1,6 +1,6 @@ # Prevent abbreviations -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/relative-url-style.md b/docs/rules/relative-url-style.md index 822a6b0bee..c2e3bd523d 100644 --- a/docs/rules/relative-url-style.md +++ b/docs/rules/relative-url-style.md @@ -1,6 +1,6 @@ # Enforce consistent relative URL style -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/require-array-join-separator.md b/docs/rules/require-array-join-separator.md index 2b2d0daece..ec9d20c5e9 100644 --- a/docs/rules/require-array-join-separator.md +++ b/docs/rules/require-array-join-separator.md @@ -1,6 +1,6 @@ # Enforce using the separator argument with `Array#join()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/require-number-to-fixed-digits-argument.md b/docs/rules/require-number-to-fixed-digits-argument.md index 6f1a74dd22..73763f770e 100644 --- a/docs/rules/require-number-to-fixed-digits-argument.md +++ b/docs/rules/require-number-to-fixed-digits-argument.md @@ -1,6 +1,6 @@ # Enforce using the digits argument with `Number#toFixed()` -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/require-post-message-target-origin.md b/docs/rules/require-post-message-target-origin.md index c1e2645e2d..101727ed24 100644 --- a/docs/rules/require-post-message-target-origin.md +++ b/docs/rules/require-post-message-target-origin.md @@ -1,6 +1,6 @@ # Enforce using the `targetOrigin` argument with `window.postMessage()` -🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ’‘ This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/string-content.md b/docs/rules/string-content.md index 3a2ea1f3e8..6af2f7405f 100644 --- a/docs/rules/string-content.md +++ b/docs/rules/string-content.md @@ -1,6 +1,6 @@ # Enforce better string content -🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +🚫 This rule is _disabled_ in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/switch-case-braces.md b/docs/rules/switch-case-braces.md index d20302b9dd..8f7ac3ae8d 100644 --- a/docs/rules/switch-case-braces.md +++ b/docs/rules/switch-case-braces.md @@ -1,6 +1,6 @@ # Enforce consistent brace style for `case` clauses -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). diff --git a/docs/rules/template-indent.md b/docs/rules/template-indent.md index 804b968917..f082bceafa 100644 --- a/docs/rules/template-indent.md +++ b/docs/rules/template-indent.md @@ -1,6 +1,6 @@ # Fix whitespace-insensitive template indentation -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). @@ -69,7 +69,7 @@ Under the hood, [strip-indent](https://npmjs.com/package/strip-indent) is used t ## Options -The rule accepts lists of `tags`, `functions`, `selectors` and `comments` to match template literals. `tags` are tagged template literal identifiers, functions are names of utility functions like `stripIndent`, selectors can be any [ESLint selector](https://eslint.org/docs/developer-guide/selectors), and comments are `/* block-commented */` strings. +The rule accepts lists of `tags`, `functions`, `selectors` and `comments` to match template literals. `tags` are tagged template literal identifiers or member expression paths, functions are names or member expression paths of utility functions like `stripIndent`, selectors can be any [ESLint selector](https://eslint.org/docs/developer-guide/selectors), and comments are `/* block-commented */` strings. Default configuration: diff --git a/docs/rules/text-encoding-identifier-case.md b/docs/rules/text-encoding-identifier-case.md index f365bbb594..96391013f7 100644 --- a/docs/rules/text-encoding-identifier-case.md +++ b/docs/rules/text-encoding-identifier-case.md @@ -1,6 +1,6 @@ # Enforce consistent case for text encoding identifiers -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§πŸ’‘ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix) and manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). diff --git a/docs/rules/throw-new-error.md b/docs/rules/throw-new-error.md index 37b7b75478..72d2423d42 100644 --- a/docs/rules/throw-new-error.md +++ b/docs/rules/throw-new-error.md @@ -1,6 +1,6 @@ -# Require `new` when throwing an error +# Require `new` when creating an error -πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs). +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). @@ -12,7 +12,7 @@ While it's possible to create a new error without using the `new` keyword, it's ## Fail ```js -throw Error(); +const error = Error('unicorn'); ``` ```js @@ -20,13 +20,13 @@ throw TypeError('unicorn'); ``` ```js -throw lib.TypeError(); +throw lib.TypeError('unicorn'); ``` ## Pass ```js -throw new Error(); +const error = new Error('unicorn'); ``` ```js @@ -34,5 +34,5 @@ throw new TypeError('unicorn'); ``` ```js -throw new lib.TypeError(); +throw new lib.TypeError('unicorn'); ``` diff --git a/index.js b/index.js index ed98665e25..77bc1b1a42 100644 --- a/index.js +++ b/index.js @@ -3,8 +3,6 @@ const createDeprecatedRules = require('./rules/utils/create-deprecated-rules.js' const {loadRules} = require('./rules/utils/rule.js'); const legacyConfigBase = require('./configs/legacy-config-base.js'); const flatConfigBase = require('./configs/flat-config-base.js'); -const recommendedRules = require('./configs/recommended.js'); -const allRules = require('./configs/all.js'); const {name, version} = require('./package.json'); const deprecatedRules = createDeprecatedRules({ @@ -28,10 +26,34 @@ const deprecatedRules = createDeprecatedRules({ 'regex-shorthand': 'unicorn/better-regex', }); -const createConfig = (rules, isLegacyConfig = false) => ({ - ...(isLegacyConfig ? legacyConfigBase : flatConfigBase), - plugins: isLegacyConfig ? ['unicorn'] : {unicorn}, - rules, +const externalRules = { + // Covered by `unicorn/no-negated-condition` + 'no-negated-condition': 'off', + // Covered by `unicorn/no-nested-ternary` + 'no-nested-ternary': 'off', +}; + +const rules = loadRules(); +const recommendedRules = Object.fromEntries( + Object.entries(rules).map(([id, rule]) => [ + `unicorn/${id}`, + rule.meta.docs.recommended ? 'error' : 'off', + ]), +); +const allRules = Object.fromEntries( + Object.keys(rules).map(id => [ + `unicorn/${id}`, + 'error', + ]), +); + +const createConfig = (rules, flatConfigName = false) => ({ + ...( + flatConfigName + ? {...flatConfigBase, name: flatConfigName, plugins: {unicorn}} + : {...legacyConfigBase, plugins: ['unicorn']} + ), + rules: {...externalRules, ...rules}, }); const unicorn = { @@ -40,16 +62,16 @@ const unicorn = { version, }, rules: { - ...loadRules(), + ...rules, ...deprecatedRules, }, }; const configs = { - recommended: createConfig(recommendedRules, /* isLegacyConfig */ true), - all: createConfig(allRules, /* isLegacyConfig */ true), - 'flat/recommended': createConfig(recommendedRules), - 'flat/all': createConfig(allRules), + recommended: createConfig(recommendedRules), + all: createConfig(allRules), + 'flat/recommended': createConfig(recommendedRules, 'unicorn/flat/recommended'), + 'flat/all': createConfig(allRules, 'unicorn/flat/all'), }; module.exports = {...unicorn, configs}; diff --git a/package.json b/package.json index 8d9da4f493..205dbb79bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-unicorn", - "version": "52.0.0", + "version": "54.0.0", "description": "More than 100 powerful ESLint rules", "license": "MIT", "repository": "sindresorhus/eslint-plugin-unicorn", @@ -13,9 +13,10 @@ "main": "index.js", "sideEffects": false, "engines": { - "node": ">=16" + "node": ">=18.18" }, "scripts": { + "bundle-lodash": "echo \"export {defaultsDeep, camelCase, kebabCase, snakeCase, upperFirst, lowerFirst} from 'lodash-es';\" | npx esbuild --bundle --outfile=rules/utils/lodash.js --format=cjs", "create-rule": "node ./scripts/create-rule.mjs && npm run fix:eslint-docs", "fix": "run-p --continue-on-error fix:*", "fix:eslint-docs": "eslint-doc-generator", @@ -28,8 +29,7 @@ "lint:markdown": "markdownlint \"**/*.md\"", "lint:package-json": "npmPkgJsonLint .", "run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs", - "bundle-lodash": "echo \"export {defaultsDeep, camelCase, kebabCase, snakeCase, upperFirst, lowerFirst} from 'lodash-es';\" | npx esbuild --bundle --outfile=rules/utils/lodash.js --format=cjs", - "smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.js", + "smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.mjs", "test": "npm-run-all --continue-on-error lint test:*", "test:js": "c8 ava" }, @@ -49,12 +49,12 @@ "xo" ], "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.5", "@eslint-community/eslint-utils": "^4.4.0", - "@eslint/eslintrc": "^2.1.4", + "@eslint/eslintrc": "^3.0.2", "ci-info": "^4.0.0", "clean-regexp": "^1.0.0", - "core-js-compat": "^3.34.0", + "core-js-compat": "^3.37.0", "esquery": "^1.5.0", "indent-string": "^4.0.0", "is-builtin-module": "^3.2.1", @@ -63,39 +63,40 @@ "read-pkg-up": "^7.0.1", "regexp-tree": "^0.1.27", "regjsparser": "^0.10.0", - "semver": "^7.5.4", + "semver": "^7.6.1", "strip-indent": "^3.0.0" }, "devDependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/core": "^7.23.6", - "@babel/eslint-parser": "^7.23.3", + "@babel/code-frame": "^7.24.2", + "@babel/core": "^7.24.5", + "@babel/eslint-parser": "^7.24.5", "@lubien/fixture-beta-package": "^1.0.0-beta.1", - "@typescript-eslint/parser": "^6.15.0", - "ava": "^6.0.1", - "c8": "^8.0.1", + "@typescript-eslint/parser": "^8.0.0-alpha.12", + "ava": "^6.1.3", + "c8": "^9.1.0", "chalk": "^5.3.0", "enquirer": "^2.4.1", - "eslint": "^8.56.0", + "eslint": "^9.2.0", "eslint-ava-rule-tester": "^5.0.1", - "eslint-doc-generator": "^1.7.0", - "eslint-plugin-eslint-plugin": "^5.2.1", + "eslint-doc-generator": "1.7.0", + "eslint-plugin-eslint-plugin": "^6.1.0", "eslint-plugin-internal-rules": "file:./scripts/internal-rules/", - "eslint-remote-tester": "^3.0.1", - "eslint-remote-tester-repositories": "^1.0.1", + "eslint-remote-tester": "^4.0.0", + "eslint-remote-tester-repositories": "^2.0.0", + "espree": "^10.0.1", "execa": "^8.0.1", "listr": "^0.14.3", "lodash-es": "^4.17.21", - "markdownlint-cli": "^0.38.0", + "markdownlint-cli": "^0.40.0", "memoize": "^10.0.0", "npm-package-json-lint": "^7.1.0", - "npm-run-all2": "^6.1.1", + "npm-run-all2": "^6.1.2", "outdent": "^0.8.0", - "pretty-ms": "^8.0.0", - "typescript": "^5.3.3", - "vue-eslint-parser": "^9.3.2", - "xo": "^0.56.0", - "yaml": "^2.3.4" + "pretty-ms": "^9.0.0", + "typescript": "^5.4.5", + "vue-eslint-parser": "^9.4.2", + "xo": "^0.58.0", + "yaml": "^2.4.2" }, "peerDependencies": { "eslint": ">=8.56.0" @@ -123,7 +124,9 @@ "test/integration/{fixtures,fixtures-local}/**" ], "rules": { + "unicorn/escape-case": "off", "unicorn/expiring-todo-comments": "off", + "unicorn/no-hex-escape": "off", "unicorn/no-null": "error", "unicorn/prefer-array-flat": [ "error", @@ -173,17 +176,5 @@ } } ] - }, - "npmpackagejsonlint": { - "rules": { - "prefer-caret-version-devDependencies": [ - "error", - { - "exceptions": [ - "eslint-plugin-internal-rules" - ] - } - ] - } } } diff --git a/readme.md b/readme.md index 61ce3c5dd7..bdfcb60e9a 100644 --- a/readme.md +++ b/readme.md @@ -17,7 +17,7 @@ npm install --save-dev eslint eslint-plugin-unicorn ## Usage (`eslint.config.js`) -**Requires ESLint `>=8.23.0`.** +**Requires ESLint `>=8.56.0`.** Use a [preset config](#preset-configs-eslintconfigjs) or configure each rule in `eslint.config.js`. @@ -103,8 +103,8 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c -πŸ’Ό [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs) enabled in.\ -βœ… Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs).\ +πŸ’Ό [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs) enabled in.\ +βœ… Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).\ πŸ”§ Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\ πŸ’‘ Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions). @@ -113,6 +113,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [better-regex](docs/rules/better-regex.md) | Improve regexes by making them shorter, consistent, and safer. | βœ… | πŸ”§ | | | [catch-error-name](docs/rules/catch-error-name.md) | Enforce a specific parameter name in catch clauses. | βœ… | πŸ”§ | | | [consistent-destructuring](docs/rules/consistent-destructuring.md) | Use destructured variables over properties. | | πŸ”§ | πŸ’‘ | +| [consistent-empty-array-spread](docs/rules/consistent-empty-array-spread.md) | Prefer consistent types when spreading a ternary in an array literal. | βœ… | πŸ”§ | | | [consistent-function-scoping](docs/rules/consistent-function-scoping.md) | Move function definitions to the highest possible scope. | βœ… | | | | [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | πŸ”§ | | | [empty-brace-spaces](docs/rules/empty-brace-spaces.md) | Enforce no spaces between braces. | βœ… | πŸ”§ | | @@ -138,10 +139,13 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [no-for-loop](docs/rules/no-for-loop.md) | Do not use a `for` loop that can be replaced with a `for-of` loop. | βœ… | πŸ”§ | πŸ’‘ | | [no-hex-escape](docs/rules/no-hex-escape.md) | Enforce the use of Unicode escapes instead of hexadecimal escapes. | βœ… | πŸ”§ | | | [no-instanceof-array](docs/rules/no-instanceof-array.md) | Require `Array.isArray()` instead of `instanceof Array`. | βœ… | πŸ”§ | | +| [no-invalid-fetch-options](docs/rules/no-invalid-fetch-options.md) | Disallow invalid options in `fetch()` and `new Request()`. | βœ… | | | | [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. | βœ… | | | | [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | | | | [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. | βœ… | πŸ”§ | | +| [no-magic-array-flat-depth](docs/rules/no-magic-array-flat-depth.md) | Disallow a magic number as the `depth` argument in `Array#flat(…).` | βœ… | | | | [no-negated-condition](docs/rules/no-negated-condition.md) | Disallow negated conditions. | βœ… | πŸ”§ | | +| [no-negation-in-equality-check](docs/rules/no-negation-in-equality-check.md) | Disallow negated expression in equality check. | βœ… | | πŸ’‘ | | [no-nested-ternary](docs/rules/no-nested-ternary.md) | Disallow nested ternary expressions. | βœ… | πŸ”§ | | | [no-new-array](docs/rules/no-new-array.md) | Disallow `new Array()`. | βœ… | πŸ”§ | πŸ’‘ | | [no-new-buffer](docs/rules/no-new-buffer.md) | Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. | βœ… | πŸ”§ | πŸ’‘ | @@ -172,7 +176,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. | βœ… | πŸ”§ | | | [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()`. | βœ… | πŸ”§ | | | [prefer-array-index-of](docs/rules/prefer-array-index-of.md) | Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item. | βœ… | πŸ”§ | πŸ’‘ | -| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`. | βœ… | πŸ”§ | πŸ’‘ | +| [prefer-array-some](docs/rules/prefer-array-some.md) | Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast,findIndex,findLastIndex}(…)`. | βœ… | πŸ”§ | πŸ’‘ | | [prefer-at](docs/rules/prefer-at.md) | Prefer `.at()` method for index access and `String#charAt()`. | βœ… | πŸ”§ | πŸ’‘ | | [prefer-blob-reading-methods](docs/rules/prefer-blob-reading-methods.md) | Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)`. | βœ… | | | | [prefer-code-point](docs/rules/prefer-code-point.md) | Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`. | βœ… | | πŸ’‘ | @@ -184,7 +188,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | βœ… | | πŸ’‘ | | [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | βœ… | | | | [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | βœ… | πŸ”§ | πŸ’‘ | -| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ | +| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ | | [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | πŸ”§ | | | [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | βœ… | πŸ”§ | | | [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. | βœ… | | πŸ’‘ | @@ -205,10 +209,12 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ | | [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. | βœ… | πŸ”§ | | | [prefer-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split('')`. | βœ… | πŸ”§ | πŸ’‘ | +| [prefer-string-raw](docs/rules/prefer-string-raw.md) | Prefer using the `String.raw` tag to avoid escaping `\`. | βœ… | πŸ”§ | | | [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) | Prefer `String#replaceAll()` over regex searches with the global flag. | βœ… | πŸ”§ | | | [prefer-string-slice](docs/rules/prefer-string-slice.md) | Prefer `String#slice()` over `String#substr()` and `String#substring()`. | βœ… | πŸ”§ | | | [prefer-string-starts-ends-with](docs/rules/prefer-string-starts-ends-with.md) | Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`. | βœ… | πŸ”§ | πŸ’‘ | | [prefer-string-trim-start-end](docs/rules/prefer-string-trim-start-end.md) | Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`. | βœ… | πŸ”§ | | +| [prefer-structured-clone](docs/rules/prefer-structured-clone.md) | Prefer using `structuredClone` to create a deep clone. | βœ… | | πŸ’‘ | | [prefer-switch](docs/rules/prefer-switch.md) | Prefer `switch` over multiple `else-if`. | βœ… | πŸ”§ | | | [prefer-ternary](docs/rules/prefer-ternary.md) | Prefer ternary expressions over simple `if-else` statements. | βœ… | πŸ”§ | | | [prefer-top-level-await](docs/rules/prefer-top-level-await.md) | Prefer top-level await over top-level promises and async function calls. | βœ… | | πŸ’‘ | @@ -222,7 +228,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [switch-case-braces](docs/rules/switch-case-braces.md) | Enforce consistent brace style for `case` clauses. | βœ… | πŸ”§ | | | [template-indent](docs/rules/template-indent.md) | Fix whitespace-insensitive template indentation. | βœ… | πŸ”§ | | | [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | βœ… | πŸ”§ | πŸ’‘ | -| [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | βœ… | πŸ”§ | | +| [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when creating an error. | βœ… | πŸ”§ | | diff --git a/rules/ast/index.js b/rules/ast/index.js index b6fb7a7006..a75e53f0cc 100644 --- a/rules/ast/index.js +++ b/rules/ast/index.js @@ -25,6 +25,7 @@ module.exports = { isArrowFunctionBody: require('./is-arrow-function-body.js'), isCallExpression, isCallOrNewExpression, + isDirective: require('./is-directive.js'), isEmptyNode: require('./is-empty-node.js'), isExpressionStatement: require('./is-expression-statement.js'), isFunction: require('./is-function.js'), @@ -33,6 +34,7 @@ module.exports = { isNewExpression, isReferenceIdentifier: require('./is-reference-identifier.js'), isStaticRequire: require('./is-static-require.js'), + isTaggedTemplateLiteral: require('./is-tagged-template-literal.js'), isUndefined: require('./is-undefined.js'), functionTypes: require('./function-types.js'), diff --git a/rules/ast/is-directive.js b/rules/ast/is-directive.js new file mode 100644 index 0000000000..3de65a0eba --- /dev/null +++ b/rules/ast/is-directive.js @@ -0,0 +1,7 @@ +'use strict'; + +const isDirective = node => + node.type === 'ExpressionStatement' + && typeof node.directive === 'string'; + +module.exports = isDirective; diff --git a/rules/ast/is-reference-identifier.js b/rules/ast/is-reference-identifier.js index 7ebf894119..0794a3acd7 100644 --- a/rules/ast/is-reference-identifier.js +++ b/rules/ast/is-reference-identifier.js @@ -120,11 +120,18 @@ function isNotReference(node) { return parent.parameters.includes(node); } + // `@typescript-eslint/parse` v7 // `type Foo = { [Identifier in keyof string]: number; };` case 'TSTypeParameter': { return parent.name === node; } + // `@typescript-eslint/parse` v8 + // `type Foo = { [Identifier in keyof string]: number; };` + case 'TSMappedType': { + return parent.key === node; + } + // `type Identifier = Foo` case 'TSTypeAliasDeclaration': { return parent.id === node; diff --git a/rules/ast/is-tagged-template-literal.js b/rules/ast/is-tagged-template-literal.js new file mode 100644 index 0000000000..a21fd819bb --- /dev/null +++ b/rules/ast/is-tagged-template-literal.js @@ -0,0 +1,28 @@ +'use strict'; + +const {isNodeMatches} = require('../utils/is-node-matches.js'); + +/** +Check if the given node is a tagged template literal. + +@param {Node} node - The AST node to check. +@param {string[]} tags - The object name or key paths. +@returns {boolean} +*/ +function isTaggedTemplateLiteral(node, tags) { + if ( + node.type !== 'TemplateLiteral' + || node.parent.type !== 'TaggedTemplateExpression' + || node.parent.quasi !== node + ) { + return false; + } + + if (tags) { + return isNodeMatches(node.parent.tag, tags); + } + + return true; +} + +module.exports = isTaggedTemplateLiteral; diff --git a/rules/better-regex.js b/rules/better-regex.js index 26ef86d761..96329bbec8 100644 --- a/rules/better-regex.js +++ b/rules/better-regex.js @@ -136,6 +136,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Improve regexes by making them shorter, consistent, and safer.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/catch-error-name.js b/rules/catch-error-name.js index 0c21d0e391..58f2cdf127 100644 --- a/rules/catch-error-name.js +++ b/rules/catch-error-name.js @@ -128,6 +128,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce a specific parameter name in catch clauses.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/consistent-destructuring.js b/rules/consistent-destructuring.js index c6b20c35b9..dcd35594d2 100644 --- a/rules/consistent-destructuring.js +++ b/rules/consistent-destructuring.js @@ -157,6 +157,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Use destructured variables over properties.', + recommended: false, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/consistent-empty-array-spread.js b/rules/consistent-empty-array-spread.js new file mode 100644 index 0000000000..7a8d0328d9 --- /dev/null +++ b/rules/consistent-empty-array-spread.js @@ -0,0 +1,126 @@ +'use strict'; +const {getStaticValue} = require('@eslint-community/eslint-utils'); + +const MESSAGE_ID = 'consistent-empty-array-spread'; +const messages = { + [MESSAGE_ID]: 'Prefer using empty {{replacementDescription}} since the {{anotherNodePosition}} is {{anotherNodeDescription}}.', +}; + +const isEmptyArrayExpression = node => + node.type === 'ArrayExpression' + && node.elements.length === 0; + +const isEmptyStringLiteral = node => + node.type === 'Literal' + && node.value === ''; + +const isString = (node, context) => { + const staticValueResult = getStaticValue(node, context.sourceCode.getScope(node)); + return typeof staticValueResult?.value === 'string'; +}; + +const isArray = (node, context) => { + if (node.type === 'ArrayExpression') { + return true; + } + + const staticValueResult = getStaticValue(node, context.sourceCode.getScope(node)); + return Array.isArray(staticValueResult?.value); +}; + +const cases = [ + { + oneSidePredicate: isEmptyStringLiteral, + anotherSidePredicate: isArray, + anotherNodeDescription: 'an array', + replacementDescription: 'array', + replacementCode: '[]', + }, + { + oneSidePredicate: isEmptyArrayExpression, + anotherSidePredicate: isString, + anotherNodeDescription: 'a string', + replacementDescription: 'string', + replacementCode: '\'\'', + }, +]; + +function createProblem({ + problemNode, + anotherNodePosition, + anotherNodeDescription, + replacementDescription, + replacementCode, +}) { + return { + node: problemNode, + messageId: MESSAGE_ID, + data: { + replacementDescription, + anotherNodePosition, + anotherNodeDescription, + }, + fix: fixer => fixer.replaceText(problemNode, replacementCode), + }; +} + +function getProblem(conditionalExpression, context) { + const { + consequent, + alternate, + } = conditionalExpression; + + for (const problemCase of cases) { + const { + oneSidePredicate, + anotherSidePredicate, + } = problemCase; + + if (oneSidePredicate(consequent, context) && anotherSidePredicate(alternate, context)) { + return createProblem({ + ...problemCase, + problemNode: consequent, + anotherNodePosition: 'alternate', + }); + } + + if (oneSidePredicate(alternate, context) && anotherSidePredicate(consequent, context)) { + return createProblem({ + ...problemCase, + problemNode: alternate, + anotherNodePosition: 'consequent', + }); + } + } +} + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => ({ + * ArrayExpression(arrayExpression) { + for (const element of arrayExpression.elements) { + if ( + element?.type !== 'SpreadElement' + || element.argument.type !== 'ConditionalExpression' + ) { + continue; + } + + yield getProblem(element.argument, context); + } + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Prefer consistent types when spreading a ternary in an array literal.', + recommended: true, + }, + fixable: 'code', + + messages, + }, +}; diff --git a/rules/consistent-function-scoping.js b/rules/consistent-function-scoping.js index 6ef9975250..b4199125cf 100644 --- a/rules/consistent-function-scoping.js +++ b/rules/consistent-function-scoping.js @@ -216,6 +216,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Move function definitions to the highest possible scope.', + recommended: true, }, schema, messages, diff --git a/rules/custom-error-definition.js b/rules/custom-error-definition.js index a4975b3163..23ce253769 100644 --- a/rules/custom-error-definition.js +++ b/rules/custom-error-definition.js @@ -208,6 +208,7 @@ module.exports = { type: 'problem', docs: { description: 'Enforce correct `Error` subclassing.', + recommended: false, }, fixable: 'code', messages, diff --git a/rules/empty-brace-spaces.js b/rules/empty-brace-spaces.js index b8c86e0cce..927cfba1fb 100644 --- a/rules/empty-brace-spaces.js +++ b/rules/empty-brace-spaces.js @@ -65,6 +65,7 @@ module.exports = { type: 'layout', docs: { description: 'Enforce no spaces between braces.', + recommended: true, }, fixable: 'whitespace', messages, diff --git a/rules/error-message.js b/rules/error-message.js index 880fe9ee37..5871630eee 100644 --- a/rules/error-message.js +++ b/rules/error-message.js @@ -98,6 +98,7 @@ module.exports = { type: 'problem', docs: { description: 'Enforce passing a `message` value when creating a built-in error.', + recommended: true, }, messages, }, diff --git a/rules/escape-case.js b/rules/escape-case.js index a4c8f54e5c..6c34aadae4 100644 --- a/rules/escape-case.js +++ b/rules/escape-case.js @@ -1,6 +1,10 @@ 'use strict'; const {replaceTemplateElement} = require('./fix/index.js'); -const {isRegexLiteral, isStringLiteral} = require('./ast/index.js'); +const { + isRegexLiteral, + isStringLiteral, + isTaggedTemplateLiteral, +} = require('./ast/index.js'); const MESSAGE_ID = 'escape-case'; const messages = { @@ -42,11 +46,17 @@ const create = context => { } }); - context.on('TemplateElement', node => getProblem({ - node, - original: node.value.raw, - fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed), - })); + context.on('TemplateElement', node => { + if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { + return; + } + + return getProblem({ + node, + original: node.value.raw, + fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed), + }); + }); }; /** @type {import('eslint').Rule.RuleModule} */ @@ -56,6 +66,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Require escape sequences to use uppercase values.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/expiring-todo-comments.js b/rules/expiring-todo-comments.js index 1a069dc880..f38a50adb4 100644 --- a/rules/expiring-todo-comments.js +++ b/rules/expiring-todo-comments.js @@ -186,7 +186,15 @@ function getPackageHelpers(dirname) { return afterArguments; } - return {packageResult, hasPackage, packageJson, packageDependencies, parseArgument, parseTodoMessage, parseTodoWithArguments}; + return { + packageResult, + hasPackage, + packageJson, + packageDependencies, + parseArgument, + parseTodoMessage, + parseTodoWithArguments, + }; } const DEPENDENCY_INCLUSION_RE = /^[+-]\s*@?\S+\/?\S+/; @@ -197,7 +205,7 @@ const ISO8601_DATE = /\d{4}-\d{2}-\d{2}/; function createArgumentGroup(arguments_) { const groups = {}; for (const {value, type} of arguments_) { - groups[type] = groups[type] || []; + groups[type] ??= []; groups[type].push(value); } @@ -573,6 +581,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Add expiration conditions to TODO comments.', + recommended: true, }, schema, messages, diff --git a/rules/explicit-length-check.js b/rules/explicit-length-check.js index 86a6cc7aea..5fcbf5c013 100644 --- a/rules/explicit-length-check.js +++ b/rules/explicit-length-check.js @@ -194,7 +194,12 @@ function create(context) { } if (node) { - return getProblem({node, isZeroLengthCheck, lengthNode, autoFix}); + return getProblem({ + node, + isZeroLengthCheck, + lengthNode, + autoFix, + }); } }, }; @@ -220,6 +225,7 @@ module.exports = { type: 'problem', docs: { description: 'Enforce explicitly comparing the `length` or `size` property of a value.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/filename-case.js b/rules/filename-case.js index 9ec7cbed01..8fdaf69d63 100644 --- a/rules/filename-case.js +++ b/rules/filename-case.js @@ -284,6 +284,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce a case style for filenames.', + recommended: true, }, schema, messages, diff --git a/rules/fix/switch-call-expression-to-new-expression.js b/rules/fix/switch-call-expression-to-new-expression.js index fc3073527c..813e3dfdf0 100644 --- a/rules/fix/switch-call-expression-to-new-expression.js +++ b/rules/fix/switch-call-expression-to-new-expression.js @@ -1,8 +1,10 @@ 'use strict'; const {isParenthesized} = require('../utils/parentheses.js'); const shouldAddParenthesesToNewExpressionCallee = require('../utils/should-add-parentheses-to-new-expression-callee.js'); +const fixSpaceAroundKeyword = require('./fix-space-around-keywords.js'); function * switchCallExpressionToNewExpression(node, sourceCode, fixer) { + yield * fixSpaceAroundKeyword(fixer, node, sourceCode); yield fixer.insertTextBefore(node, 'new '); const {callee} = node; diff --git a/rules/import-style.js b/rules/import-style.js index 6db6674db4..6674d06349 100644 --- a/rules/import-style.js +++ b/rules/import-style.js @@ -363,6 +363,7 @@ module.exports = { type: 'problem', docs: { description: 'Enforce specific import styles per module.', + recommended: true, }, schema, messages, diff --git a/rules/new-for-builtins.js b/rules/new-for-builtins.js index 559e50cdf5..b60844653d 100644 --- a/rules/new-for-builtins.js +++ b/rules/new-for-builtins.js @@ -78,6 +78,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-abusive-eslint-disable.js b/rules/no-abusive-eslint-disable.js index 927dabe748..9bd759e041 100644 --- a/rules/no-abusive-eslint-disable.js +++ b/rules/no-abusive-eslint-disable.js @@ -42,6 +42,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce specifying rules to disable in `eslint-disable` comments.', + recommended: true, }, messages, }, diff --git a/rules/no-anonymous-default-export.js b/rules/no-anonymous-default-export.js index 8195ec7796..3fe29706ea 100644 --- a/rules/no-anonymous-default-export.js +++ b/rules/no-anonymous-default-export.js @@ -205,6 +205,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow anonymous functions and classes as the default export.', + recommended: true, }, hasSuggestions: true, messages, diff --git a/rules/no-array-callback-reference.js b/rules/no-array-callback-reference.js index e658bbab9a..6325c586cc 100644 --- a/rules/no-array-callback-reference.js +++ b/rules/no-array-callback-reference.js @@ -278,6 +278,7 @@ module.exports = { type: 'problem', docs: { description: 'Prevent passing a function reference directly to iterator methods.', + recommended: true, }, hasSuggestions: true, messages, diff --git a/rules/no-array-for-each.js b/rules/no-array-for-each.js index 0bc633835d..025ffff593 100644 --- a/rules/no-array-for-each.js +++ b/rules/no-array-for-each.js @@ -347,7 +347,12 @@ function isFixable(callExpression, {scope, functionInfo, allIdentifiers, sourceC // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1814 || (parameters.length === 2 && parameters[1].type !== 'Identifier') || parameters.some(({type, typeAnnotation}) => type === 'RestElement' || typeAnnotation) - || !isFunctionParametersSafeToFix(callback, {scope, callExpression, allIdentifiers, sourceCode}) + || !isFunctionParametersSafeToFix(callback, { + scope, + callExpression, + allIdentifiers, + sourceCode, + }) ) { return false; } @@ -434,7 +439,12 @@ const create = context => { messageId: MESSAGE_ID_ERROR, }; - if (!isFixable(node, {scope, allIdentifiers, functionInfo, sourceCode})) { + if (!isFixable(node, { + scope, + allIdentifiers, + functionInfo, + sourceCode, + })) { yield problem; continue; } @@ -465,6 +475,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `for…of` over the `forEach` method.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-array-method-this-argument.js b/rules/no-array-method-this-argument.js index 31f70fe04d..ee50d3a586 100644 --- a/rules/no-array-method-this-argument.js +++ b/rules/no-array-method-this-argument.js @@ -184,12 +184,12 @@ const create = context => { }); }); - // `Array.from()` + // `Array.from()` and `Array.fromAsync()` context.on('CallExpression', callExpression => { if ( !isMethodCall(callExpression, { object: 'Array', - method: 'from', + methods: ['from', 'fromAsync'], argumentsLength: 3, optionalCall: false, optionalMember: false, @@ -216,6 +216,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow using the `this` argument in array methods.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-array-push-push.js b/rules/no-array-push-push.js index 6d356f8e62..96c2719b08 100644 --- a/rules/no-array-push-push.js +++ b/rules/no-array-push-push.js @@ -1,6 +1,9 @@ 'use strict'; -const {hasSideEffect, isCommaToken, isSemicolonToken} = require('@eslint-community/eslint-utils'); -const getCallExpressionArgumentsText = require('./utils/get-call-expression-arguments-text.js'); +const {hasSideEffect, isSemicolonToken} = require('@eslint-community/eslint-utils'); +const { + getCallExpressionTokens, + getCallExpressionArgumentsText, +} = require('./utils/index.js'); const isSameReference = require('./utils/is-same-reference.js'); const {isNodeMatches} = require('./utils/is-node-matches.js'); const getPreviousNode = require('./utils/get-previous-node.js'); @@ -78,13 +81,17 @@ function create(context) { const fix = function * (fixer) { if (secondCallArguments.length > 0) { - const text = getCallExpressionArgumentsText(secondCall, sourceCode); + const text = getCallExpressionArgumentsText(sourceCode, secondCall); + + const { + trailingCommaToken, + closingParenthesisToken, + } = getCallExpressionTokens(sourceCode, firstCall); - const [penultimateToken, lastToken] = sourceCode.getLastTokens(firstCall, 2); yield ( - isCommaToken(penultimateToken) - ? fixer.insertTextAfter(penultimateToken, ` ${text}`) - : fixer.insertTextBefore(lastToken, firstCall.arguments.length > 0 ? `, ${text}` : text) + trailingCommaToken + ? fixer.insertTextAfter(trailingCommaToken, ` ${text}`) + : fixer.insertTextBefore(closingParenthesisToken, firstCall.arguments.length > 0 ? `, ${text}` : text) ); } @@ -135,6 +142,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce combining multiple `Array#push()` into one call.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-array-reduce.js b/rules/no-array-reduce.js index 5c62068997..9a7057130a 100644 --- a/rules/no-array-reduce.js +++ b/rules/no-array-reduce.js @@ -119,6 +119,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow `Array#reduce()` and `Array#reduceRight()`.', + recommended: true, }, schema, messages, diff --git a/rules/no-await-expression-member.js b/rules/no-await-expression-member.js index f4bf8f06d5..c7cfd8206c 100644 --- a/rules/no-await-expression-member.js +++ b/rules/no-await-expression-member.js @@ -83,6 +83,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow member access from await expression.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-await-in-promise-methods.js b/rules/no-await-in-promise-methods.js index 4e428558bd..7ece11f2c4 100644 --- a/rules/no-await-in-promise-methods.js +++ b/rules/no-await-in-promise-methods.js @@ -61,6 +61,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow using `await` in `Promise` method parameters.', + recommended: true, }, hasSuggestions: true, messages, diff --git a/rules/no-console-spaces.js b/rules/no-console-spaces.js index c0f583e893..558fe24f31 100644 --- a/rules/no-console-spaces.js +++ b/rules/no-console-spaces.js @@ -79,6 +79,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Do not use leading/trailing space between `console.log` parameters.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-document-cookie.js b/rules/no-document-cookie.js index c9db79352c..ac95ccc21b 100644 --- a/rules/no-document-cookie.js +++ b/rules/no-document-cookie.js @@ -19,6 +19,7 @@ module.exports = { type: 'problem', docs: { description: 'Do not use `document.cookie` directly.', + recommended: true, }, messages, }, diff --git a/rules/no-empty-file.js b/rules/no-empty-file.js index dc3a396917..f833886e05 100644 --- a/rules/no-empty-file.js +++ b/rules/no-empty-file.js @@ -1,12 +1,11 @@ 'use strict'; -const {isEmptyNode} = require('./ast/index.js'); +const {isEmptyNode, isDirective} = require('./ast/index.js'); const MESSAGE_ID = 'no-empty-file'; const messages = { [MESSAGE_ID]: 'Empty files are not allowed.', }; -const isDirective = node => node.type === 'ExpressionStatement' && typeof node.directive === 'string'; const isEmpty = node => isEmptyNode(node, isDirective); const isTripleSlashDirective = node => @@ -51,6 +50,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow empty files.', + recommended: true, }, messages, }, diff --git a/rules/no-for-loop.js b/rules/no-for-loop.js index f3c0d0ffb9..c7f3916c72 100644 --- a/rules/no-for-loop.js +++ b/rules/no-for-loop.js @@ -419,6 +419,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Do not use a `for` loop that can be replaced with a `for-of` loop.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-hex-escape.js b/rules/no-hex-escape.js index 52acbe4751..f57b093b01 100644 --- a/rules/no-hex-escape.js +++ b/rules/no-hex-escape.js @@ -1,6 +1,10 @@ 'use strict'; const {replaceTemplateElement} = require('./fix/index.js'); -const {isStringLiteral, isRegexLiteral} = require('./ast/index.js'); +const { + isStringLiteral, + isRegexLiteral, + isTaggedTemplateLiteral, +} = require('./ast/index.js'); const MESSAGE_ID = 'no-hex-escape'; const messages = { @@ -29,7 +33,13 @@ const create = context => ({ return checkEscape(context, node, node.raw); } }, - TemplateElement: node => checkEscape(context, node, node.value.raw), + TemplateElement(node) { + if (isTaggedTemplateLiteral(node.parent, ['String.raw'])) { + return; + } + + return checkEscape(context, node, node.value.raw); + }, }); /** @type {import('eslint').Rule.RuleModule} */ @@ -39,6 +49,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce the use of Unicode escapes instead of hexadecimal escapes.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-instanceof-array.js b/rules/no-instanceof-array.js index cc6689478c..539b8e7560 100644 --- a/rules/no-instanceof-array.js +++ b/rules/no-instanceof-array.js @@ -58,6 +58,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Require `Array.isArray()` instead of `instanceof Array`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-invalid-fetch-options.js b/rules/no-invalid-fetch-options.js new file mode 100644 index 0000000000..8989f0478e --- /dev/null +++ b/rules/no-invalid-fetch-options.js @@ -0,0 +1,111 @@ +'use strict'; +const {getStaticValue} = require('@eslint-community/eslint-utils'); +const { + isCallExpression, + isNewExpression, + isUndefined, + isNullLiteral, +} = require('./ast/index.js'); + +const MESSAGE_ID_ERROR = 'no-invalid-fetch-options'; +const messages = { + [MESSAGE_ID_ERROR]: '"body" is not allowed when method is "{{method}}".', +}; + +const isObjectPropertyWithName = (node, name) => + node.type === 'Property' + && !node.computed + && node.key.type === 'Identifier' + && node.key.name === name; + +function checkFetchOptions(context, node) { + if (node.type !== 'ObjectExpression') { + return; + } + + const {properties} = node; + + const bodyProperty = properties.findLast(property => isObjectPropertyWithName(property, 'body')); + + if (!bodyProperty) { + return; + } + + const bodyValue = bodyProperty.value; + if (isUndefined(bodyValue) || isNullLiteral(bodyValue)) { + return; + } + + const methodProperty = properties.findLast(property => isObjectPropertyWithName(property, 'method')); + // If `method` is omitted but there is an `SpreadElement`, we just ignore the case + if (!methodProperty) { + if (properties.some(node => node.type === 'SpreadElement')) { + return; + } + + return { + node: bodyProperty.key, + messageId: MESSAGE_ID_ERROR, + data: {method: 'GET'}, + }; + } + + const methodValue = methodProperty.value; + + const scope = context.sourceCode.getScope(methodValue); + let method = getStaticValue(methodValue, scope)?.value; + + if (typeof method !== 'string') { + return; + } + + method = method.toUpperCase(); + if (method !== 'GET' && method !== 'HEAD') { + return; + } + + return { + node: bodyProperty.key, + messageId: MESSAGE_ID_ERROR, + data: {method}, + }; +} + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => { + context.on('CallExpression', callExpression => { + if (!isCallExpression(callExpression, { + name: 'fetch', + minimumArguments: 2, + optional: false, + })) { + return; + } + + return checkFetchOptions(context, callExpression.arguments[1]); + }); + + context.on('NewExpression', newExpression => { + if (!isNewExpression(newExpression, { + name: 'Request', + minimumArguments: 2, + })) { + return; + } + + return checkFetchOptions(context, newExpression.arguments[1]); + }); +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'problem', + docs: { + description: 'Disallow invalid options in `fetch()` and `new Request()`.', + recommended: true, + }, + messages, + }, +}; diff --git a/rules/no-invalid-remove-event-listener.js b/rules/no-invalid-remove-event-listener.js index 017f275b2f..24115de226 100644 --- a/rules/no-invalid-remove-event-listener.js +++ b/rules/no-invalid-remove-event-listener.js @@ -54,6 +54,7 @@ module.exports = { type: 'problem', docs: { description: 'Prevent calling `EventTarget#removeEventListener()` with the result of an expression.', + recommended: true, }, messages, }, diff --git a/rules/no-keyword-prefix.js b/rules/no-keyword-prefix.js index 17bdd8edc2..f595edad0d 100644 --- a/rules/no-keyword-prefix.js +++ b/rules/no-keyword-prefix.js @@ -192,6 +192,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow identifiers starting with `new` or `class`.', + recommended: false, }, schema, messages, diff --git a/rules/no-lonely-if.js b/rules/no-lonely-if.js index 6e2989307a..4f224d8f4b 100644 --- a/rules/no-lonely-if.js +++ b/rules/no-lonely-if.js @@ -144,6 +144,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow `if` statements as the only statement in `if` blocks without `else`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-magic-array-flat-depth.js b/rules/no-magic-array-flat-depth.js new file mode 100644 index 0000000000..a3d54494b5 --- /dev/null +++ b/rules/no-magic-array-flat-depth.js @@ -0,0 +1,54 @@ +'use strict'; +const {isMethodCall, isNumberLiteral} = require('./ast/index.js'); +const {getCallExpressionTokens} = require('./utils/index.js'); + +const MESSAGE_ID = 'no-magic-array-flat-depth'; +const messages = { + [MESSAGE_ID]: 'Magic number as depth is not allowed.', +}; + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => ({ + CallExpression(callExpression) { + if (!isMethodCall(callExpression, { + method: 'flat', + argumentsLength: 1, + optionalCall: false, + })) { + return; + } + + const [depth] = callExpression.arguments; + + if (!isNumberLiteral(depth) || depth.value === 1) { + return; + } + + const {sourceCode} = context; + const { + openingParenthesisToken, + closingParenthesisToken, + } = getCallExpressionTokens(sourceCode, callExpression); + if (sourceCode.commentsExistBetween(openingParenthesisToken, closingParenthesisToken)) { + return; + } + + return { + node: depth, + messageId: MESSAGE_ID, + }; + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Disallow a magic number as the `depth` argument in `Array#flat(…).`', + recommended: true, + }, + messages, + }, +}; diff --git a/rules/no-negated-condition.js b/rules/no-negated-condition.js index 2ebaa70bb1..7af4db6774 100644 --- a/rules/no-negated-condition.js +++ b/rules/no-negated-condition.js @@ -137,6 +137,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow negated conditions.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-negation-in-equality-check.js b/rules/no-negation-in-equality-check.js new file mode 100644 index 0000000000..9bcdbb8115 --- /dev/null +++ b/rules/no-negation-in-equality-check.js @@ -0,0 +1,104 @@ +'use strict'; +const { + fixSpaceAroundKeyword, + addParenthesizesToReturnOrThrowExpression, +} = require('./fix/index.js'); +const { + needsSemicolon, + isParenthesized, + isOnSameLine, +} = require('./utils/index.js'); + +const MESSAGE_ID_ERROR = 'no-negation-in-equality-check/error'; +const MESSAGE_ID_SUGGESTION = 'no-negation-in-equality-check/suggestion'; +const messages = { + [MESSAGE_ID_ERROR]: 'Negated expression in not allowed in equality check.', + [MESSAGE_ID_SUGGESTION]: 'Switch to \'{{operator}}\' check.', +}; + +const EQUALITY_OPERATORS = new Set([ + '===', + '!==', + '==', + '!=', +]); + +const isEqualityCheck = node => node.type === 'BinaryExpression' && EQUALITY_OPERATORS.has(node.operator); +const isNegatedExpression = node => node.type === 'UnaryExpression' && node.prefix && node.operator === '!'; + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => ({ + BinaryExpression(binaryExpression) { + const {operator, left} = binaryExpression; + + if ( + !isEqualityCheck(binaryExpression) + || !isNegatedExpression(left) + ) { + return; + } + + const {sourceCode} = context; + const bangToken = sourceCode.getFirstToken(left); + const negatedOperator = `${operator.startsWith('!') ? '=' : '!'}${operator.slice(1)}`; + + return { + node: bangToken, + messageId: MESSAGE_ID_ERROR, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + suggest: [ + { + messageId: MESSAGE_ID_SUGGESTION, + data: { + operator: negatedOperator, + }, + /** @param {import('eslint').Rule.RuleFixer} fixer */ + * fix(fixer) { + yield * fixSpaceAroundKeyword(fixer, binaryExpression, sourceCode); + + const tokenAfterBang = sourceCode.getTokenAfter(bangToken); + + const {parent} = binaryExpression; + if ( + (parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement') + && !isParenthesized(binaryExpression, sourceCode) + ) { + const returnToken = sourceCode.getFirstToken(parent); + if (!isOnSameLine(returnToken, tokenAfterBang)) { + yield * addParenthesizesToReturnOrThrowExpression(fixer, parent, sourceCode); + } + } + + yield fixer.remove(bangToken); + + const previousToken = sourceCode.getTokenBefore(bangToken); + if (needsSemicolon(previousToken, sourceCode, tokenAfterBang.value)) { + yield fixer.insertTextAfter(bangToken, ';'); + } + + const operatorToken = sourceCode.getTokenAfter( + left, + token => token.type === 'Punctuator' && token.value === operator, + ); + yield fixer.replaceText(operatorToken, negatedOperator); + }, + }, + ], + }; + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'problem', + docs: { + description: 'Disallow negated expression in equality check.', + recommended: true, + }, + + hasSuggestions: true, + messages, + }, +}; diff --git a/rules/no-nested-ternary.js b/rules/no-nested-ternary.js index 61e01cf1a4..57f3ea8182 100644 --- a/rules/no-nested-ternary.js +++ b/rules/no-nested-ternary.js @@ -51,6 +51,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow nested ternary expressions.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-new-array.js b/rules/no-new-array.js index cc8c813448..7cd8f3d166 100644 --- a/rules/no-new-array.js +++ b/rules/no-new-array.js @@ -96,6 +96,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow `new Array()`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-new-buffer.js b/rules/no-new-buffer.js index 4c03eac12e..42c529264d 100644 --- a/rules/no-new-buffer.js +++ b/rules/no-new-buffer.js @@ -90,6 +90,7 @@ module.exports = { type: 'problem', docs: { description: 'Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-null.js b/rules/no-null.js index b639969ef9..b18fc9cf95 100644 --- a/rules/no-null.js +++ b/rules/no-null.js @@ -144,6 +144,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow the use of the `null` literal.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-object-as-default-parameter.js b/rules/no-object-as-default-parameter.js index 0b61566b6a..49cd9ad655 100644 --- a/rules/no-object-as-default-parameter.js +++ b/rules/no-object-as-default-parameter.js @@ -44,6 +44,7 @@ module.exports = { type: 'problem', docs: { description: 'Disallow the use of objects as default parameters.', + recommended: true, }, messages, }, diff --git a/rules/no-process-exit.js b/rules/no-process-exit.js index dd84e0e760..1cc6ea9988 100644 --- a/rules/no-process-exit.js +++ b/rules/no-process-exit.js @@ -98,6 +98,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow `process.exit()`.', + recommended: true, }, messages, }, diff --git a/rules/no-single-promise-in-promise-methods.js b/rules/no-single-promise-in-promise-methods.js index b0090f1c2a..94e30744a7 100644 --- a/rules/no-single-promise-in-promise-methods.js +++ b/rules/no-single-promise-in-promise-methods.js @@ -159,6 +159,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow passing single-element arrays to `Promise` methods.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-static-only-class.js b/rules/no-static-only-class.js index ed19172e52..c320bc6da0 100644 --- a/rules/no-static-only-class.js +++ b/rules/no-static-only-class.js @@ -217,6 +217,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow classes that only have static members.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-thenable.js b/rules/no-thenable.js index 2aa903bd2f..238c8b8749 100644 --- a/rules/no-thenable.js +++ b/rules/no-thenable.js @@ -192,6 +192,7 @@ module.exports = { type: 'problem', docs: { description: 'Disallow `then` property.', + recommended: true, }, messages, }, diff --git a/rules/no-this-assignment.js b/rules/no-this-assignment.js index a7d4975f73..7ba5b97b7d 100644 --- a/rules/no-this-assignment.js +++ b/rules/no-this-assignment.js @@ -32,6 +32,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow assigning `this` to a variable.', + recommended: true, }, messages, }, diff --git a/rules/no-typeof-undefined.js b/rules/no-typeof-undefined.js index 261dfe0207..54a5306464 100644 --- a/rules/no-typeof-undefined.js +++ b/rules/no-typeof-undefined.js @@ -134,6 +134,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow comparing `undefined` using `typeof`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/no-unnecessary-await.js b/rules/no-unnecessary-await.js index addae9666f..f0b9516766 100644 --- a/rules/no-unnecessary-await.js +++ b/rules/no-unnecessary-await.js @@ -99,6 +99,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow awaiting non-promise values.', + recommended: true, }, fixable: 'code', diff --git a/rules/no-unnecessary-polyfills.js b/rules/no-unnecessary-polyfills.js index 5ccacb38c0..7ab4e904d0 100644 --- a/rules/no-unnecessary-polyfills.js +++ b/rules/no-unnecessary-polyfills.js @@ -23,13 +23,13 @@ const additionalPolyfillPatterns = { const prefixes = '(mdn-polyfills/|polyfill-)'; const suffixes = '(-polyfill)'; -const delimiter = '(\\.|-|\\.prototype\\.|/)?'; +const delimiter = String.raw`(\.|-|\.prototype\.|/)?`; const polyfills = Object.keys(compatData).map(feature => { let [ecmaVersion, constructorName, methodName = ''] = feature.split('.'); if (ecmaVersion === 'es') { - ecmaVersion = '(es\\d*)'; + ecmaVersion = String.raw`(es\d*)`; } constructorName = `(${constructorName}|${camelCase(constructorName)})`; @@ -169,6 +169,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce the use of built-in methods instead of unnecessary polyfills.', + recommended: true, }, schema, messages, diff --git a/rules/no-unreadable-array-destructuring.js b/rules/no-unreadable-array-destructuring.js index daa41fe6fa..6e17b4720b 100644 --- a/rules/no-unreadable-array-destructuring.js +++ b/rules/no-unreadable-array-destructuring.js @@ -76,6 +76,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow unreadable array destructuring.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-unreadable-iife.js b/rules/no-unreadable-iife.js index 17a25687f4..d8f0098340 100644 --- a/rules/no-unreadable-iife.js +++ b/rules/no-unreadable-iife.js @@ -38,6 +38,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow unreadable IIFEs.', + recommended: true, }, hasSuggestions: false, messages, diff --git a/rules/no-unused-properties.js b/rules/no-unused-properties.js index 0f190d4eb0..912d48f933 100644 --- a/rules/no-unused-properties.js +++ b/rules/no-unused-properties.js @@ -232,6 +232,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow unused object properties.', + recommended: false, }, messages, }, diff --git a/rules/no-useless-fallback-in-spread.js b/rules/no-useless-fallback-in-spread.js index 68c081527f..1598173f65 100644 --- a/rules/no-useless-fallback-in-spread.js +++ b/rules/no-useless-fallback-in-spread.js @@ -60,6 +60,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow useless fallback when spreading in object literals.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-useless-length-check.js b/rules/no-useless-length-check.js index 2b9b611bfc..d2fc8aedf1 100644 --- a/rules/no-useless-length-check.js +++ b/rules/no-useless-length-check.js @@ -145,6 +145,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow useless array length check.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-useless-promise-resolve-reject.js b/rules/no-useless-promise-resolve-reject.js index e96daed3b4..f99afbba68 100644 --- a/rules/no-useless-promise-resolve-reject.js +++ b/rules/no-useless-promise-resolve-reject.js @@ -111,7 +111,7 @@ function fix(callExpression, isInTryStatement, sourceCode) { if (isReject) { // `return Promise.reject()` -> `throw undefined` - text = text || 'undefined'; + text ||= 'undefined'; text = `throw ${text}`; if (isYieldExpression) { @@ -143,7 +143,7 @@ function fix(callExpression, isInTryStatement, sourceCode) { } // `=> Promise.resolve()` -> `=> {}` - text = text || '{}'; + text ||= '{}'; } } @@ -205,6 +205,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-useless-spread.js b/rules/no-useless-spread.js index e76e0f21ce..ef43965a75 100644 --- a/rules/no-useless-spread.js +++ b/rules/no-useless-spread.js @@ -374,6 +374,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow unnecessary spread.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/no-useless-switch-case.js b/rules/no-useless-switch-case.js index bff988c5b5..84d622f030 100644 --- a/rules/no-useless-switch-case.js +++ b/rules/no-useless-switch-case.js @@ -16,31 +16,17 @@ const create = context => ({ * SwitchStatement(switchStatement) { const {cases} = switchStatement; - // TypeScript allows multiple `default` cases - const defaultCases = cases.filter(switchCase => switchCase.test === null); - if (defaultCases.length !== 1) { - return; - } - - const [defaultCase] = defaultCases; - // We only check cases where the last case is the `default` case - if (defaultCase !== cases.at(-1)) { + if (cases.length < 2 || cases.at(-1).test !== null) { return; } - const uselessCases = []; - for (let index = cases.length - 2; index >= 0; index--) { const node = cases[index]; - if (isEmptySwitchCase(node)) { - uselessCases.unshift(node); - } else { + if (!isEmptySwitchCase(node)) { break; } - } - for (const node of uselessCases) { yield { node, loc: getSwitchCaseHeadLocation(node, context.sourceCode), @@ -64,6 +50,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow useless case in switch statements.', + recommended: true, }, hasSuggestions: true, messages, diff --git a/rules/no-useless-undefined.js b/rules/no-useless-undefined.js index 3aba0e8b2a..4a24698a6a 100644 --- a/rules/no-useless-undefined.js +++ b/rules/no-useless-undefined.js @@ -293,6 +293,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow useless `undefined`.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/no-zero-fractions.js b/rules/no-zero-fractions.js index 372c3a844d..185a023904 100644 --- a/rules/no-zero-fractions.js +++ b/rules/no-zero-fractions.js @@ -72,6 +72,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Disallow number literals with zero fractions or dangling dots.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/number-literal-case.js b/rules/number-literal-case.js index 26ad799439..6d93da187c 100644 --- a/rules/number-literal-case.js +++ b/rules/number-literal-case.js @@ -45,6 +45,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce proper case for numeric literals.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/numeric-separators-style.js b/rules/numeric-separators-style.js index 9c0ccaf649..7b713c1cf9 100644 --- a/rules/numeric-separators-style.js +++ b/rules/numeric-separators-style.js @@ -173,6 +173,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce the style of numeric separators by correctly grouping digits.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/prefer-add-event-listener.js b/rules/prefer-add-event-listener.js index 85a132d239..f40b8545b0 100644 --- a/rules/prefer-add-event-listener.js +++ b/rules/prefer-add-event-listener.js @@ -89,7 +89,7 @@ const create = context => { }, ReturnStatement(node) { - codePathInfo.returnsSomething = codePathInfo.returnsSomething || Boolean(node.argument); + codePathInfo.returnsSomething ||= Boolean(node.argument); }, 'AssignmentExpression:exit'(node) { @@ -180,6 +180,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/prefer-array-find.js b/rules/prefer-array-find.js index 6a81ae233f..85ff943cf1 100644 --- a/rules/prefer-array-find.js +++ b/rules/prefer-array-find.js @@ -180,7 +180,7 @@ const create = context => { const { checkFromLast, } = { - checkFromLast: false, + checkFromLast: true, ...context.options[0], }; @@ -428,8 +428,8 @@ const schema = [ properties: { checkFromLast: { type: 'boolean', - // TODO: Change default value to `true`, or remove the option when targeting Node.js 18. - default: false, + // TODO: Remove the option at some point. + default: true, }, }, }, @@ -442,6 +442,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-array-flat-map.js b/rules/prefer-array-flat-map.js index 186666ae33..5b4e444f3d 100644 --- a/rules/prefer-array-flat-map.js +++ b/rules/prefer-array-flat-map.js @@ -75,6 +75,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `.flatMap(…)` over `.map(…).flat()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-array-flat.js b/rules/prefer-array-flat.js index 363469add3..854ff24a80 100644 --- a/rules/prefer-array-flat.js +++ b/rules/prefer-array-flat.js @@ -271,6 +271,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Array#flat()` over legacy techniques to flatten arrays.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/prefer-array-index-of.js b/rules/prefer-array-index-of.js index 42ae76ff53..f211e3c5be 100644 --- a/rules/prefer-array-index-of.js +++ b/rules/prefer-array-index-of.js @@ -21,6 +21,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Array#{indexOf,lastIndexOf}()` over `Array#{findIndex,findLastIndex}()` when looking for the index of an item.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-array-some.js b/rules/prefer-array-some.js index 5ffc51059b..3da1cf5409 100644 --- a/rules/prefer-array-some.js +++ b/rules/prefer-array-some.js @@ -40,10 +40,14 @@ const isCheckingUndefined = node => && isLiteral(node.parent.right, null) ) ); +const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1; +const isLiteralZero = node => isLiteral(node, 0); /** @param {import('eslint').Rule.RuleContext} context */ -const create = context => ({ - CallExpression(callExpression) { +const create = context => { + // `.find(…)` + // `.findLast(…)` + context.on('CallExpression', callExpression => { if (!isMethodCall(callExpression, { methods: ['find', 'findLast'], minimumArguments: 1, @@ -86,8 +90,61 @@ const create = context => ({ }, ], }; - }, - BinaryExpression(binaryExpression) { + }); + + // These operators also used in `prefer-includes`, try to reuse the code in future + // `.{findIndex,findLastIndex}(…) !== -1` + // `.{findIndex,findLastIndex}(…) != -1` + // `.{findIndex,findLastIndex}(…) > -1` + // `.{findIndex,findLastIndex}(…) === -1` + // `.{findIndex,findLastIndex}(…) == -1` + // `.{findIndex,findLastIndex}(…) >= 0` + // `.{findIndex,findLastIndex}(…) < 0` + context.on('BinaryExpression', binaryExpression => { + const {left, right, operator} = binaryExpression; + + if (!( + isMethodCall(left, { + methods: ['findIndex', 'findLastIndex'], + argumentsLength: 1, + optionalCall: false, + optionalMember: false, + }) + && ( + (['!==', '!=', '>', '===', '=='].includes(operator) && isNegativeOne(right)) + || (['>=', '<'].includes(operator) && isLiteralZero(right)) + ) + )) { + return; + } + + const methodNode = left.callee.property; + return { + node: methodNode, + messageId: ERROR_ID_ARRAY_SOME, + data: {method: methodNode.name}, + * fix(fixer) { + if (['===', '==', '<'].includes(operator)) { + yield fixer.insertTextBefore(binaryExpression, '!'); + } + + yield fixer.replaceText(methodNode, 'some'); + + const operatorToken = context.sourceCode.getTokenAfter( + left, + token => token.type === 'Punctuator' && token.value === operator, + ); + const [start] = operatorToken.range; + const [, end] = binaryExpression.range; + + yield fixer.removeRange([start, end]); + }, + }; + }); + + // `.filter(…).length > 0` + // `.filter(…).length !== 0` + context.on('BinaryExpression', binaryExpression => { if (!( // We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule. (binaryExpression.operator === '>' || binaryExpression.operator === '!==') @@ -139,8 +196,8 @@ const create = context => ({ // The `BinaryExpression` always ends with a number or `)`, no need check for ASI }, }; - }, -}); + }); +}; /** @type {import('eslint').Rule.RuleModule} */ module.exports = { @@ -148,7 +205,8 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`.', + description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast,findIndex,findLastIndex}(…)`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-at.js b/rules/prefer-at.js index b337bcd684..9986a8e10d 100644 --- a/rules/prefer-at.js +++ b/rules/prefer-at.js @@ -365,6 +365,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `.at()` method for index access and `String#charAt()`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-blob-reading-methods.js b/rules/prefer-blob-reading-methods.js index bbb1bb418b..e7a892b763 100644 --- a/rules/prefer-blob-reading-methods.js +++ b/rules/prefer-blob-reading-methods.js @@ -39,6 +39,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Blob#arrayBuffer()` over `FileReader#readAsArrayBuffer(…)` and `Blob#text()` over `FileReader#readAsText(…)`.', + recommended: true, }, messages, }, diff --git a/rules/prefer-code-point.js b/rules/prefer-code-point.js index f94317f393..3d72bf82ca 100644 --- a/rules/prefer-code-point.js +++ b/rules/prefer-code-point.js @@ -60,6 +60,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `String#codePointAt(…)` over `String#charCodeAt(…)` and `String.fromCodePoint(…)` over `String.fromCharCode(…)`.', + recommended: true, }, hasSuggestions: true, messages, diff --git a/rules/prefer-date-now.js b/rules/prefer-date-now.js index 3d46f07366..b9cbc008cf 100644 --- a/rules/prefer-date-now.js +++ b/rules/prefer-date-now.js @@ -128,6 +128,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-default-parameters.js b/rules/prefer-default-parameters.js index 8bb0fe4b44..c5bb54b73f 100644 --- a/rules/prefer-default-parameters.js +++ b/rules/prefer-default-parameters.js @@ -208,6 +208,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer default parameters over reassignment.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-dom-node-append.js b/rules/prefer-dom-node-append.js index 43526b7b33..ea3634e350 100644 --- a/rules/prefer-dom-node-append.js +++ b/rules/prefer-dom-node-append.js @@ -41,6 +41,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Node#append()` over `Node#appendChild()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-dom-node-dataset.js b/rules/prefer-dom-node-dataset.js index db4bfdd6b4..abc6456a4f 100644 --- a/rules/prefer-dom-node-dataset.js +++ b/rules/prefer-dom-node-dataset.js @@ -91,6 +91,17 @@ const create = context => ({ return; } + const method = callExpression.callee.property.name; + // Playwright's `Locator#getAttribute()` returns a promise. + // https://playwright.dev/docs/api/class-locator#locator-get-attribute + if ( + callExpression.parent.type === 'AwaitExpression' + && callExpression.parent.argument === callExpression + && method === 'getAttribute' + ) { + return; + } + const attributeName = callExpression.arguments[0].value.toLowerCase(); if (!attributeName.startsWith('data-')) { @@ -113,6 +124,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer using `.dataset` on DOM elements over calling attribute methods.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-dom-node-remove.js b/rules/prefer-dom-node-remove.js index 1411ec51a0..3b64ac6b0f 100644 --- a/rules/prefer-dom-node-remove.js +++ b/rules/prefer-dom-node-remove.js @@ -114,6 +114,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-dom-node-text-content.js b/rules/prefer-dom-node-text-content.js index 03b09a749c..72ff975856 100644 --- a/rules/prefer-dom-node-text-content.js +++ b/rules/prefer-dom-node-text-content.js @@ -68,6 +68,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `.textContent` over `.innerText`.', + recommended: true, }, hasSuggestions: true, messages, diff --git a/rules/prefer-event-target.js b/rules/prefer-event-target.js index 9d4373d4d0..68a4b0b691 100644 --- a/rules/prefer-event-target.js +++ b/rules/prefer-event-target.js @@ -111,6 +111,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `EventTarget` over `EventEmitter`.', + recommended: true, }, messages, }, diff --git a/rules/prefer-export-from.js b/rules/prefer-export-from.js index 5fb9455784..5dec4345ae 100644 --- a/rules/prefer-export-from.js +++ b/rules/prefer-export-from.js @@ -126,9 +126,7 @@ function getFixFunction({ exportDeclaration = exportDeclarations.find(({source, exportKind}) => source.value === sourceValue && exportKind === 'type'); } - if (!exportDeclaration) { - exportDeclaration = exportDeclarations.find(({source, exportKind}) => source.value === sourceValue && exportKind !== 'type'); - } + exportDeclaration ||= exportDeclarations.find(({source, exportKind}) => source.value === sourceValue && exportKind !== 'type'); /** @param {import('eslint').Rule.RuleFixer} fixer */ return function * (fixer) { @@ -404,6 +402,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `export…from` when re-exporting.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-includes.js b/rules/prefer-includes.js index 43ada06615..e9e878beb0 100644 --- a/rules/prefer-includes.js +++ b/rules/prefer-includes.js @@ -5,9 +5,9 @@ const {isLiteral} = require('./ast/index.js'); const MESSAGE_ID = 'prefer-includes'; const messages = { - [MESSAGE_ID]: 'Use `.includes()`, rather than `.indexOf()`, when checking for existence.', + [MESSAGE_ID]: 'Use `.includes()`, rather than `.{{method}}()`, when checking for existence.', }; -// Ignore {_,lodash,underscore}.indexOf +// Ignore `{_,lodash,underscore}.{indexOf,lastIndexOf}` const ignoredVariables = new Set(['_', 'lodash', 'underscore']); const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name); const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1; @@ -30,6 +30,9 @@ const getProblem = (context, node, target, argumentsNodes) => { return { node: memberExpressionNode.property, messageId: MESSAGE_ID, + data: { + method: node.left.callee.property.name, + }, fix(fixer) { const replacement = `${isNegativeResult(node) ? '!' : ''}${targetSource}.includes(${argumentsSource.join(', ')})`; return fixer.replaceText(node, replacement); @@ -49,7 +52,7 @@ const create = context => { context.on('BinaryExpression', node => { const {left, right, operator} = node; - if (!isMethodNamed(left, 'indexOf')) { + if (!isMethodNamed(left, 'indexOf') && !isMethodNamed(left, 'lastIndexOf')) { return; } @@ -86,7 +89,8 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence.', + description: 'Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-json-parse-buffer.js b/rules/prefer-json-parse-buffer.js index d58c9aa916..585b173794 100644 --- a/rules/prefer-json-parse-buffer.js +++ b/rules/prefer-json-parse-buffer.js @@ -152,6 +152,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer reading a JSON file as a buffer.', + recommended: false, }, fixable: 'code', messages, diff --git a/rules/prefer-keyboard-event-key.js b/rules/prefer-keyboard-event-key.js index 65642332e9..8230de582c 100644 --- a/rules/prefer-keyboard-event-key.js +++ b/rules/prefer-keyboard-event-key.js @@ -179,6 +179,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-logical-operator-over-ternary.js b/rules/prefer-logical-operator-over-ternary.js index 084b798c1e..240855c93b 100644 --- a/rules/prefer-logical-operator-over-ternary.js +++ b/rules/prefer-logical-operator-over-ternary.js @@ -151,6 +151,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer using a logical operator over a ternary.', + recommended: true, }, hasSuggestions: true, diff --git a/rules/prefer-math-trunc.js b/rules/prefer-math-trunc.js index 430af5f2e9..a90e4e4148 100644 --- a/rules/prefer-math-trunc.js +++ b/rules/prefer-math-trunc.js @@ -101,6 +101,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce the use of `Math.trunc` instead of bitwise operators.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-modern-dom-apis.js b/rules/prefer-modern-dom-apis.js index 75acf8cf31..b06e502663 100644 --- a/rules/prefer-modern-dom-apis.js +++ b/rules/prefer-modern-dom-apis.js @@ -134,6 +134,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-modern-math-apis.js b/rules/prefer-modern-math-apis.js index 35c23fcca5..1cb6fb605b 100644 --- a/rules/prefer-modern-math-apis.js +++ b/rules/prefer-modern-math-apis.js @@ -205,6 +205,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer modern `Math` APIs over legacy patterns.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-module.js b/rules/prefer-module.js index 5ffb0e3794..a1f3bdee37 100644 --- a/rules/prefer-module.js +++ b/rules/prefer-module.js @@ -1,7 +1,7 @@ 'use strict'; -const {isOpeningParenToken} = require('@eslint-community/eslint-utils'); const isShadowed = require('./utils/is-shadowed.js'); const assertToken = require('./utils/assert-token.js'); +const {getCallExpressionTokens} = require('./utils/index.js'); const {isStaticRequire, isReferenceIdentifier, isFunction} = require('./ast/index.js'); const { removeParentheses, @@ -26,7 +26,7 @@ const messages = { [SUGGESTION_USE_STRICT_DIRECTIVE]: 'Remove "use strict" directive.', [SUGGESTION_IMPORT_META_DIRNAME]: 'Replace `__dirname` with `import.meta.dirname`.', [SUGGESTION_IMPORT_META_URL_TO_DIRNAME]: 'Replace `__dirname` with `…(import.meta.url)`.', - [SUGGESTION_IMPORT_META_FILENAME]: 'Replace `__dirname` with `import.meta.filename`.', + [SUGGESTION_IMPORT_META_FILENAME]: 'Replace `__filename` with `import.meta.filename`.', [SUGGESTION_IMPORT_META_URL_TO_FILENAME]: 'Replace `__filename` with `…(import.meta.url)`.', [SUGGESTION_IMPORT]: 'Switch to `import`.', [SUGGESTION_EXPORT]: 'Switch to `export`.', @@ -77,12 +77,12 @@ function fixRequireCall(node, sourceCode) { if (parent.type === 'ExpressionStatement' && parent.parent.type === 'Program') { return function * (fixer) { yield fixer.replaceText(callee, 'import'); - const openingParenthesisToken = sourceCode.getTokenAfter( - callee, - isOpeningParenToken, - ); + + const { + openingParenthesisToken, + closingParenthesisToken, + } = getCallExpressionTokens(sourceCode, requireCall); yield fixer.replaceText(openingParenthesisToken, ' '); - const closingParenthesisToken = sourceCode.getLastToken(requireCall); yield fixer.remove(closingParenthesisToken); for (const node of [callee, requireCall, source]) { @@ -137,12 +137,12 @@ function fixRequireCall(node, sourceCode) { yield fixer.replaceText(equalToken, ' from '); yield fixer.remove(callee); - const openingParenthesisToken = sourceCode.getTokenAfter( - callee, - isOpeningParenToken, - ); + + const { + openingParenthesisToken, + closingParenthesisToken, + } = getCallExpressionTokens(sourceCode, requireCall); yield fixer.remove(openingParenthesisToken); - const closingParenthesisToken = sourceCode.getLastToken(requireCall); yield fixer.remove(closingParenthesisToken); for (const node of [callee, requireCall, source]) { @@ -372,6 +372,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer JavaScript modules (ESM) over CommonJS.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-native-coercion-functions.js b/rules/prefer-native-coercion-functions.js index 1eff6bc390..deb9a18085 100644 --- a/rules/prefer-native-coercion-functions.js +++ b/rules/prefer-native-coercion-functions.js @@ -178,6 +178,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-negative-index.js b/rules/prefer-negative-index.js index 09255cbe47..5d528d3e57 100644 --- a/rules/prefer-negative-index.js +++ b/rules/prefer-negative-index.js @@ -206,6 +206,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer negative index over `.length - index` when possible.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-node-protocol.js b/rules/prefer-node-protocol.js index c436eecfef..6515a1a600 100644 --- a/rules/prefer-node-protocol.js +++ b/rules/prefer-node-protocol.js @@ -55,6 +55,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer using the `node:` protocol when importing Node.js builtin modules.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-number-properties.js b/rules/prefer-number-properties.js index 294804487d..3c9e641ca2 100644 --- a/rules/prefer-number-properties.js +++ b/rules/prefer-number-properties.js @@ -77,16 +77,25 @@ function checkProperty({node, path: [name]}, sourceCode) { const create = context => { const { checkInfinity, + checkNaN, } = { - checkInfinity: true, + checkInfinity: false, + checkNaN: true, ...context.options[0], }; const {sourceCode} = context; - let objects = Object.keys(globalObjects); - if (!checkInfinity) { - objects = objects.filter(name => name !== 'Infinity'); - } + const objects = Object.keys(globalObjects).filter(name => { + if (!checkInfinity && name === 'Infinity') { + return false; + } + + if (!checkNaN && name === 'NaN') { + return false; + } + + return true; + }); const tracker = new GlobalReferenceTracker({ objects, @@ -103,6 +112,10 @@ const schema = [ additionalProperties: false, properties: { checkInfinity: { + type: 'boolean', + default: false, + }, + checkNaN: { type: 'boolean', default: true, }, @@ -117,6 +130,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Number` static properties over global ones.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-object-from-entries.js b/rules/prefer-object-from-entries.js index 6170daf5f9..6c5c46aae2 100644 --- a/rules/prefer-object-from-entries.js +++ b/rules/prefer-object-from-entries.js @@ -244,6 +244,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/prefer-optional-catch-binding.js b/rules/prefer-optional-catch-binding.js index 8e3d5187a4..c0a94680a5 100644 --- a/rules/prefer-optional-catch-binding.js +++ b/rules/prefer-optional-catch-binding.js @@ -68,6 +68,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer omitting the `catch` binding parameter.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-prototype-methods.js b/rules/prefer-prototype-methods.js index 363622ee93..7aeccc5b74 100644 --- a/rules/prefer-prototype-methods.js +++ b/rules/prefer-prototype-methods.js @@ -153,6 +153,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer borrowing methods from the prototype instead of the instance.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-query-selector.js b/rules/prefer-query-selector.js index b7d685e626..832f419cd2 100644 --- a/rules/prefer-query-selector.js +++ b/rules/prefer-query-selector.js @@ -128,6 +128,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-reflect-apply.js b/rules/prefer-reflect-apply.js index 013e897137..5aec914039 100644 --- a/rules/prefer-reflect-apply.js +++ b/rules/prefer-reflect-apply.js @@ -90,6 +90,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Reflect.apply()` over `Function#apply()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-regexp-test.js b/rules/prefer-regexp-test.js index ecff31099e..f0ccd0ddaa 100644 --- a/rules/prefer-regexp-test.js +++ b/rules/prefer-regexp-test.js @@ -148,6 +148,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-set-has.js b/rules/prefer-set-has.js index b1113cb8b1..3015ccce80 100644 --- a/rules/prefer-set-has.js +++ b/rules/prefer-set-has.js @@ -178,6 +178,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-set-size.js b/rules/prefer-set-size.js index 4a2d967532..4ad0892c1e 100644 --- a/rules/prefer-set-size.js +++ b/rules/prefer-set-size.js @@ -96,6 +96,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer using `Set#size` instead of `Array#length`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-spread.js b/rules/prefer-spread.js index 27a7921a38..afe661322d 100644 --- a/rules/prefer-spread.js +++ b/rules/prefer-spread.js @@ -508,6 +508,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer the spread operator over `Array.from(…)`, `Array#concat(…)`, `Array#{slice,toSpliced}()` and `String#split(\'\')`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-string-raw.js b/rules/prefer-string-raw.js new file mode 100644 index 0000000000..134e0e5d02 --- /dev/null +++ b/rules/prefer-string-raw.js @@ -0,0 +1,94 @@ +'use strict'; +const {isStringLiteral, isDirective} = require('./ast/index.js'); +const {fixSpaceAroundKeyword} = require('./fix/index.js'); + +const MESSAGE_ID = 'prefer-string-raw'; +const messages = { + [MESSAGE_ID]: '`String.raw` should be used to avoid escaping `\\`.', +}; + +const BACKSLASH = '\\'; + +function unescapeBackslash(raw) { + const quote = raw.charAt(0); + + raw = raw.slice(1, -1); + + let result = ''; + for (let position = 0; position < raw.length; position++) { + const character = raw[position]; + if (character === BACKSLASH) { + const nextCharacter = raw[position + 1]; + if (nextCharacter === BACKSLASH || nextCharacter === quote) { + result += nextCharacter; + position++; + continue; + } + } + + result += character; + } + + return result; +} + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => { + context.on('Literal', node => { + if ( + !isStringLiteral(node) + || isDirective(node.parent) + || ( + ( + node.parent.type === 'ImportDeclaration' + || node.parent.type === 'ExportNamedDeclaration' + || node.parent.type === 'ExportAllDeclaration' + ) && node.parent.source === node + ) + || (node.parent.type === 'Property' && !node.parent.computed && node.parent.key === node) + || (node.parent.type === 'JSXAttribute' && node.parent.value === node) + || (node.parent.type === 'TSEnumMember' && (node.parent.initializer === node || node.parent.id === node)) + ) { + return; + } + + const {raw} = node; + if ( + raw.at(-2) === BACKSLASH + || !raw.includes(BACKSLASH + BACKSLASH) + || raw.includes('`') + || raw.includes('${') + || node.loc.start.line !== node.loc.end.line + ) { + return; + } + + const unescaped = unescapeBackslash(raw); + if (unescaped !== node.value) { + return; + } + + return { + node, + messageId: MESSAGE_ID, + * fix(fixer) { + yield fixer.replaceText(node, `String.raw\`${unescaped}\``); + yield * fixSpaceAroundKeyword(fixer, node, context.sourceCode); + }, + }; + }); +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Prefer using the `String.raw` tag to avoid escaping `\\`.', + recommended: true, + }, + fixable: 'code', + messages, + }, +}; diff --git a/rules/prefer-string-replace-all.js b/rules/prefer-string-replace-all.js index cd0d01161b..9c223a50b2 100644 --- a/rules/prefer-string-replace-all.js +++ b/rules/prefer-string-replace-all.js @@ -138,6 +138,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `String#replaceAll()` over regex searches with the global flag.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-string-slice.js b/rules/prefer-string-slice.js index e4b9419e3f..386de12f20 100644 --- a/rules/prefer-string-slice.js +++ b/rules/prefer-string-slice.js @@ -162,7 +162,12 @@ const create = context => ({ } const fixArguments = method === 'substr' ? fixSubstrArguments : fixSubstringArguments; - yield * fixArguments({node, fixer, context, abort}); + yield * fixArguments({ + node, + fixer, + context, + abort, + }); }, }; }, @@ -175,6 +180,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `String#slice()` over `String#substr()` and `String#substring()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-string-starts-ends-with.js b/rules/prefer-string-starts-ends-with.js index 834556a92d..a05a34c0d6 100644 --- a/rules/prefer-string-starts-ends-with.js +++ b/rules/prefer-string-starts-ends-with.js @@ -191,6 +191,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/prefer-string-trim-start-end.js b/rules/prefer-string-trim-start-end.js index 343ef75d5e..d24bb72dc4 100644 --- a/rules/prefer-string-trim-start-end.js +++ b/rules/prefer-string-trim-start-end.js @@ -37,6 +37,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prefer-structured-clone.js b/rules/prefer-structured-clone.js new file mode 100644 index 0000000000..70b7768c42 --- /dev/null +++ b/rules/prefer-structured-clone.js @@ -0,0 +1,151 @@ +'use strict'; +const {isCallExpression, isMethodCall} = require('./ast/index.js'); +const {removeParentheses} = require('./fix/index.js'); +const { + isNodeMatchesNameOrPath, + getCallExpressionTokens, +} = require('./utils/index.js'); + +const MESSAGE_ID_ERROR = 'prefer-structured-clone/error'; +const MESSAGE_ID_SUGGESTION = 'prefer-structured-clone/suggestion'; +const messages = { + [MESSAGE_ID_ERROR]: 'Prefer `structuredClone(…)` over `{{description}}` to create a deep clone.', + [MESSAGE_ID_SUGGESTION]: 'Switch to `structuredClone(…)`.', +}; + +const lodashCloneDeepFunctions = [ + '_.cloneDeep', + 'lodash.cloneDeep', +]; + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => { + const {functions: configFunctions} = { + functions: [], + ...context.options[0], + }; + const functions = [...configFunctions, ...lodashCloneDeepFunctions]; + + // `JSON.parse(JSON.stringify(…))` + context.on('CallExpression', callExpression => { + if (!( + // `JSON.stringify()` + isMethodCall(callExpression, { + object: 'JSON', + method: 'parse', + argumentsLength: 1, + optionalCall: false, + optionalMember: false, + }) + // `JSON.parse()` + && isMethodCall(callExpression.arguments[0], { + object: 'JSON', + method: 'stringify', + argumentsLength: 1, + optionalCall: false, + optionalMember: false, + }) + )) { + return; + } + + const jsonParse = callExpression; + const jsonStringify = callExpression.arguments[0]; + + return { + node: jsonParse, + loc: { + start: jsonParse.loc.start, + end: jsonStringify.callee.loc.end, + }, + messageId: MESSAGE_ID_ERROR, + data: { + description: 'JSON.parse(JSON.stringify(…))', + }, + suggest: [ + { + messageId: MESSAGE_ID_SUGGESTION, + * fix(fixer) { + yield fixer.replaceText(jsonParse.callee, 'structuredClone'); + + const {sourceCode} = context; + + yield fixer.remove(jsonStringify.callee); + yield * removeParentheses(jsonStringify.callee, fixer, sourceCode); + + const { + openingParenthesisToken, + closingParenthesisToken, + trailingCommaToken, + } = getCallExpressionTokens(sourceCode, jsonStringify); + + yield fixer.remove(openingParenthesisToken); + yield fixer.remove(closingParenthesisToken); + if (trailingCommaToken) { + yield fixer.remove(trailingCommaToken); + } + }, + }, + ], + }; + }); + + // `_.cloneDeep(foo)` + context.on('CallExpression', callExpression => { + if (!isCallExpression(callExpression, { + argumentsLength: 1, + optional: false, + })) { + return; + } + + const {callee} = callExpression; + const matchedFunction = functions.find(nameOrPath => isNodeMatchesNameOrPath(callee, nameOrPath)); + + if (!matchedFunction) { + return; + } + + return { + node: callee, + messageId: MESSAGE_ID_ERROR, + data: { + description: `${matchedFunction.trim()}(…)`, + }, + suggest: [ + { + messageId: MESSAGE_ID_SUGGESTION, + fix: fixer => fixer.replaceText(callee, 'structuredClone'), + }, + ], + }; + }); +}; + +const schema = [ + { + type: 'object', + additionalProperties: false, + properties: { + functions: { + type: 'array', + uniqueItems: true, + }, + }, + }, +]; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Prefer using `structuredClone` to create a deep clone.', + recommended: true, + }, + hasSuggestions: true, + schema, + messages, + }, +}; diff --git a/rules/prefer-switch.js b/rules/prefer-switch.js index 28723d0858..57662b9864 100644 --- a/rules/prefer-switch.js +++ b/rules/prefer-switch.js @@ -336,6 +336,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer `switch` over multiple `else-if`.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/prefer-ternary.js b/rules/prefer-ternary.js index a1f8ab9163..934af8a5e5 100644 --- a/rules/prefer-ternary.js +++ b/rules/prefer-ternary.js @@ -272,6 +272,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer ternary expressions over simple `if-else` statements.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/prefer-top-level-await.js b/rules/prefer-top-level-await.js index 509dcf939c..3505f1c6fa 100644 --- a/rules/prefer-top-level-await.js +++ b/rules/prefer-top-level-await.js @@ -145,6 +145,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prefer top-level await over top-level promises and async function calls.', + recommended: true, }, hasSuggestions: true, messages, diff --git a/rules/prefer-type-error.js b/rules/prefer-type-error.js index 48849d853a..b08ae4dcef 100644 --- a/rules/prefer-type-error.js +++ b/rules/prefer-type-error.js @@ -144,6 +144,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce throwing `TypeError` in type checking conditions.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/prevent-abbreviations.js b/rules/prevent-abbreviations.js index 5e5d382f66..cb10e1324d 100644 --- a/rules/prevent-abbreviations.js +++ b/rules/prevent-abbreviations.js @@ -116,7 +116,7 @@ const getNameReplacements = (name, options, limit = 3) => { } // Split words - const words = name.split(/(?=[^a-z])|(?<=[^A-Za-z])/).filter(Boolean); + const words = name.split(/(?=\P{Lowercase_Letter})|(?<=\P{Letter})/u).filter(Boolean); let hasReplacements = false; const combinations = words.map(word => { @@ -637,6 +637,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Prevent abbreviations.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/relative-url-style.js b/rules/relative-url-style.js index b1b2e83123..7236dd28ef 100644 --- a/rules/relative-url-style.js +++ b/rules/relative-url-style.js @@ -159,6 +159,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce consistent relative URL style.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/require-array-join-separator.js b/rules/require-array-join-separator.js index bceb236e11..90f17098c1 100644 --- a/rules/require-array-join-separator.js +++ b/rules/require-array-join-separator.js @@ -56,6 +56,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce using the separator argument with `Array#join()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/require-number-to-fixed-digits-argument.js b/rules/require-number-to-fixed-digits-argument.js index a9e2ed4026..1eb88dbbdd 100644 --- a/rules/require-number-to-fixed-digits-argument.js +++ b/rules/require-number-to-fixed-digits-argument.js @@ -47,6 +47,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce using the digits argument with `Number#toFixed()`.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/require-post-message-target-origin.js b/rules/require-post-message-target-origin.js index 981aa6e08d..d43159d66e 100644 --- a/rules/require-post-message-target-origin.js +++ b/rules/require-post-message-target-origin.js @@ -64,6 +64,9 @@ module.exports = { type: 'problem', docs: { description: 'Enforce using the `targetOrigin` argument with `window.postMessage()`.', + // Turned off because we can't distinguish `window.postMessage` and `{Worker,MessagePort,Client,BroadcastChannel}#postMessage()` + // See #1396 + recommended: false, }, hasSuggestions: true, messages, diff --git a/rules/string-content.js b/rules/string-content.js index 646d6f696f..66f998b565 100644 --- a/rules/string-content.js +++ b/rules/string-content.js @@ -166,7 +166,8 @@ const schema = [ additionalProperties: false, }, ], - }}, + }, + }, }, }, ]; @@ -178,6 +179,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce better string content.', + recommended: false, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/switch-case-braces.js b/rules/switch-case-braces.js index 6dd094e4c1..530bc0e0d7 100644 --- a/rules/switch-case-braces.js +++ b/rules/switch-case-braces.js @@ -101,6 +101,7 @@ module.exports = { type: 'layout', docs: { description: 'Enforce consistent brace style for `case` clauses.', + recommended: true, }, fixable: 'code', schema: [{enum: ['always', 'avoid']}], diff --git a/rules/template-indent.js b/rules/template-indent.js index 560fd36e03..e702007820 100644 --- a/rules/template-indent.js +++ b/rules/template-indent.js @@ -3,7 +3,12 @@ const stripIndent = require('strip-indent'); const indentString = require('indent-string'); const esquery = require('esquery'); const {replaceTemplateElement} = require('./fix/index.js'); -const {isMethodCall, isCallExpression} = require('./ast/index.js'); +const { + isMethodCall, + isCallExpression, + isTaggedTemplateLiteral, +} = require('./ast/index.js'); +const {isNodeMatches} = require('./utils/index.js'); const MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE = 'template-indent'; const messages = { @@ -114,10 +119,7 @@ const create = context => { if ( options.tags.length > 0 - && node.parent.type === 'TaggedTemplateExpression' - && node.parent.quasi === node - && node.parent.tag.type === 'Identifier' - && options.tags.includes(node.parent.tag.name) + && isTaggedTemplateLiteral(node, options.tags) ) { return true; } @@ -126,8 +128,7 @@ const create = context => { options.functions.length > 0 && node.parent.type === 'CallExpression' && node.parent.arguments.includes(node) - && node.parent.callee.type === 'Identifier' - && options.functions.includes(node.parent.callee.name) + && isNodeMatches(node.parent.callee, options.functions) ) { return true; } @@ -211,6 +212,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Fix whitespace-insensitive template indentation.', + recommended: true, }, fixable: 'code', schema, diff --git a/rules/text-encoding-identifier-case.js b/rules/text-encoding-identifier-case.js index 21729ab32b..992c310859 100644 --- a/rules/text-encoding-identifier-case.js +++ b/rules/text-encoding-identifier-case.js @@ -100,6 +100,7 @@ module.exports = { type: 'suggestion', docs: { description: 'Enforce consistent case for text encoding identifiers.', + recommended: true, }, fixable: 'code', hasSuggestions: true, diff --git a/rules/throw-new-error.js b/rules/throw-new-error.js index c8fb4476f3..5c509795cb 100644 --- a/rules/throw-new-error.js +++ b/rules/throw-new-error.js @@ -3,7 +3,7 @@ const {switchCallExpressionToNewExpression} = require('./fix/index.js'); const messageId = 'throw-new-error'; const messages = { - [messageId]: 'Use `new` when throwing an error.', + [messageId]: 'Use `new` when creating an error.', }; const customError = /^(?:[A-Z][\da-z]*)*Error$/; @@ -11,13 +11,6 @@ const customError = /^(?:[A-Z][\da-z]*)*Error$/; /** @param {import('eslint').Rule.RuleContext} context */ const create = context => ({ CallExpression(node) { - if (!( - node.parent.type === 'ThrowStatement' - && node.parent.argument === node - )) { - return; - } - const {callee} = node; if (!( (callee.type === 'Identifier' && customError.test(callee.name)) @@ -45,7 +38,8 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'Require `new` when throwing an error.', + description: 'Require `new` when creating an error.', + recommended: true, }, fixable: 'code', messages, diff --git a/rules/utils/escape-template-element-raw.js b/rules/utils/escape-template-element-raw.js index 14bc08c40a..8612a449fc 100644 --- a/rules/utils/escape-template-element-raw.js +++ b/rules/utils/escape-template-element-raw.js @@ -2,6 +2,6 @@ const escapeTemplateElementRaw = string => string.replaceAll( /(?<=(?:^|[^\\])(?:\\\\)*)(?(?:`|\$(?={)))/g, - '\\$', + String.raw`\$`, ); module.exports = escapeTemplateElementRaw; diff --git a/rules/utils/get-call-expression-arguments-text.js b/rules/utils/get-call-expression-arguments-text.js index eeaad51751..b84655be49 100644 --- a/rules/utils/get-call-expression-arguments-text.js +++ b/rules/utils/get-call-expression-arguments-text.js @@ -1,21 +1,26 @@ 'use strict'; -const {isOpeningParenToken} = require('@eslint-community/eslint-utils'); +const getCallExpressionTokens = require('./get-call-expression-tokens.js'); + +/** @typedef {import('estree').CallExpression} CallExpression */ /** Get the text of the arguments list of `CallExpression`. -@param {Node} node - The `CallExpression` node. +@param {import('eslint').SourceCode} sourceCode - The source code object. +@param {CallExpression} callExpression - The `CallExpression` node. @param {SourceCode} sourceCode - The source code object. @returns {string} */ -const getCallExpressionArgumentsText = (node, sourceCode) => { - const openingParenthesisToken = sourceCode.getTokenAfter(node.callee, isOpeningParenToken); - const closingParenthesisToken = sourceCode.getLastToken(node); +function getCallExpressionArgumentsText(sourceCode, callExpression) { + const { + openingParenthesisToken, + closingParenthesisToken, + } = getCallExpressionTokens(sourceCode, callExpression); return sourceCode.text.slice( openingParenthesisToken.range[1], closingParenthesisToken.range[0], ); -}; +} module.exports = getCallExpressionArgumentsText; diff --git a/rules/utils/get-call-expression-tokens.js b/rules/utils/get-call-expression-tokens.js new file mode 100644 index 0000000000..81b2cb5137 --- /dev/null +++ b/rules/utils/get-call-expression-tokens.js @@ -0,0 +1,37 @@ +'use strict'; + +const { + isOpeningParenToken, + isCommaToken, +} = require('@eslint-community/eslint-utils'); + +/** @typedef {import('estree').CallExpression} CallExpression */ +/** @typedef {import('eslint').AST.Token} Token */ + +/** +Get the `openingParenthesisToken`, `closingParenthesisToken`, and `trailingCommaToken` of `CallExpression`. + +@param {import('eslint').SourceCode} sourceCode - The source code object. +@param {CallExpression} callExpression - The `CallExpression` node. +@returns {{ + openingParenthesisToken: Token, + closingParenthesisToken: Token, + trailingCommaToken: Token | undefined, +}} +*/ +function getCallExpressionTokens(sourceCode, callExpression) { + const openingParenthesisToken = sourceCode.getTokenAfter(callExpression.callee, isOpeningParenToken); + const [ + penultimateToken, + closingParenthesisToken, + ] = sourceCode.getLastTokens(callExpression, 2); + const trailingCommaToken = isCommaToken(penultimateToken) ? penultimateToken : undefined; + + return { + openingParenthesisToken, + closingParenthesisToken, + trailingCommaToken, + }; +} + +module.exports = getCallExpressionTokens; diff --git a/rules/utils/index.js b/rules/utils/index.js index e57698c6c3..8ca4f54ff4 100644 --- a/rules/utils/index.js +++ b/rules/utils/index.js @@ -18,6 +18,8 @@ module.exports = { avoidCapture: require('./avoid-capture.js'), escapeString: require('./escape-string.js'), getBooleanAncestor, + getCallExpressionArgumentsText: require('./get-call-expression-arguments-text.js'), + getCallExpressionTokens: require('./get-call-expression-tokens.js'), getParentheses, getParenthesizedRange, getParenthesizedText, diff --git a/rules/utils/numeric.js b/rules/utils/numeric.js index 7b4571971f..02a2f40265 100644 --- a/rules/utils/numeric.js +++ b/rules/utils/numeric.js @@ -31,7 +31,12 @@ function parseNumber(text) { power = '', } = text.match(/^(?[\d._]*?)(?:(?[Ee])(?[+-])?(?[\d_]+))?$/).groups; - return {number, mark, sign, power}; + return { + number, + mark, + sign, + power, + }; } function parseFloatNumber(text) { diff --git a/rules/utils/rule.js b/rules/utils/rule.js index 26eaf676ee..46581a55b6 100644 --- a/rules/utils/rule.js +++ b/rules/utils/rule.js @@ -47,15 +47,11 @@ function reportListenerProblems(problems, context) { continue; } - if (problem.fix) { - problem.fix = wrapFixFunction(problem.fix); - } + problem.fix &&= wrapFixFunction(problem.fix); if (Array.isArray(problem.suggest)) { for (const suggest of problem.suggest) { - if (suggest.fix) { - suggest.fix = wrapFixFunction(suggest.fix); - } + suggest.fix &&= wrapFixFunction(suggest.fix); suggest.data = { ...problem.data, diff --git a/scripts/create-rule.mjs b/scripts/create-rule.mjs index e04b8e590c..5758272050 100644 --- a/scripts/create-rule.mjs +++ b/scripts/create-rule.mjs @@ -35,44 +35,6 @@ function renderTemplate({source, target, data}) { return fs.writeFileSync(targetFile, content); } -function updateRecommended(id) { - const RULE_INDENT = '\t'; - const file = path.join(ROOT, 'configs/recommended.js'); - const content = fs.readFileSync(file, 'utf8'); - const {before, rules, after} = content.match(/(?.*?{\n)(?.*?)(?\n};\s*)/s)?.groups ?? {}; - if (!rules) { - throw new Error('Unexpected content in β€œconfigs/recommended.js”.'); - } - - const lines = rules.split('\n'); - - if (!lines.every(line => line.startsWith(RULE_INDENT))) { - throw new Error('Unexpected content in β€œconfigs/recommended.js”.'); - } - - let ruleContent = `${RULE_INDENT}'unicorn/${id}': 'error',`; - - const unicornRuleLines = lines.filter(line => line.startsWith(`${RULE_INDENT}'unicorn/`)); - let insertIndex; - if (ruleContent.localeCompare(unicornRuleLines[0]) === -1) { - insertIndex = 0; - } else if (ruleContent.localeCompare(unicornRuleLines.at(-1)) === 1) { - insertIndex = lines.length; - lines[lines.length - 1] += ','; - ruleContent = ruleContent.slice(0, -1); - } else { - const lineBefore = unicornRuleLines[ - unicornRuleLines.findIndex(line => line.localeCompare(ruleContent) === 1) - 1 - ]; - insertIndex = lines.indexOf(lineBefore) + 1; - } - - lines.splice(insertIndex, 0, ruleContent); - - const updated = `${before}${lines.join('\n')}${after}`; - fs.writeFileSync(file, updated); -} - async function getData() { const questions = [ { @@ -153,7 +115,6 @@ renderTemplate({ target: `test/${id}.mjs`, data, }); -updateRecommended(id); try { await execa('code', [ diff --git a/scripts/template/rule.js.jst b/scripts/template/rule.js.jst index 90afdc6da3..5ff569a392 100644 --- a/scripts/template/rule.js.jst +++ b/scripts/template/rule.js.jst @@ -17,15 +17,14 @@ const messages = { }; <% } %> -const selector = [ - 'Literal', - '[value="unicorn"]', -].join(''); - /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { return { - [selector](node) { + Literal(node) { + if (node.value !== 'unicorn') { + return; + } + return { node, messageId: <% if (hasSuggestions) { %>MESSAGE_ID_ERROR<% } else { %>MESSAGE_ID<% } %>, @@ -63,6 +62,7 @@ module.exports = { type: '<%= type %>', docs: { description: '<%= description %>', + recommended: true, }, <% if (fixableType) { %>fixable: '<%= fixableType %>',<% } %> <% if (hasSuggestions) { %>hasSuggestions: true,<% } %> diff --git a/test/better-regex.mjs b/test/better-regex.mjs index 5675d04fba..69182a0900 100644 --- a/test/better-regex.mjs +++ b/test/better-regex.mjs @@ -1,8 +1,6 @@ -import {createRequire} from 'node:module'; -import {getTester} from './utils/test.mjs'; +import {getTester, parsers} from './utils/test.mjs'; const {test} = getTester(import.meta); -const require = createRequire(import.meta.url); const MESSAGE_ID = 'better-regex'; @@ -31,23 +29,23 @@ const testCase = (original, optimized) => ({ test({ valid: [ // Literal regex - 'const foo = /\\d/', - 'const foo = /\\W/i', - 'const foo = /\\w/gi', + String.raw`const foo = /\d/`, + String.raw`const foo = /\W/i`, + String.raw`const foo = /\w/gi`, 'const foo = /[a-z]/gi', - 'const foo = /\\d*?/gi', + String.raw`const foo = /\d*?/gi`, // Should not crash ESLint (#446 and #448) - '/\\{\\{verificationUrl\\}\\}/gu', - '/^test-(?[a-zA-Z-\\d]+)$/u', + String.raw`/\{\{verificationUrl\}\}/gu`, + String.raw`/^test-(?[a-zA-Z-\d]+)$/u`, String.raw`/[\p{Script_Extensions=Greek}--Ο€]/v`, // Should not suggest wrong regex (#447) - '/(\\s|\\.|@|_|-)/u', - '/[\\s.@_-]/u', + String.raw`/(\s|\.|@|_|-)/u`, + String.raw`/[\s.@_-]/u`, // Should not remove repeating patterns too easily (#769) - '/http:\\/\\/[^/]+\\/pull\\/commits/gi', + String.raw`/http:\/\/[^/]+\/pull\/commits/gi`, { code: '/[GgHhIiΓ₯.Z:a-f"0-8%A*Γ€]/', @@ -55,14 +53,14 @@ test({ }, // `RegExp()` constructor - 'new RegExp(\'\\d\')', - 'new RegExp(\'\\d\', \'ig\')', - 'new RegExp(\'\\d*?\')', + String.raw`new RegExp('\d')`, + String.raw`new RegExp('\d', 'ig')`, + String.raw`new RegExp('\d*?')`, 'new RegExp(\'[a-z]\', \'i\')', - 'new RegExp(/\\d/)', - 'new RegExp(/\\d/gi)', - 'new RegExp(/\\d/, \'ig\')', - 'new RegExp(/\\d*?/)', + String.raw`new RegExp(/\d/)`, + String.raw`new RegExp(/\d/gi)`, + String.raw`new RegExp(/\d/, 'ig')`, + String.raw`new RegExp(/\d*?/)`, 'new RegExp(/[a-z]/, \'i\')', // Not `new` 'RegExp("[0-9]")', @@ -81,105 +79,105 @@ test({ '/[ ;-]/g', // #994 - '/\\s?\\s?/', // https://github.com/DmitrySoshnikov/regexp-tree/issues/216#issuecomment-762073297 - '/\\s{0,2}/', + String.raw`/\s?\s?/`, // https://github.com/DmitrySoshnikov/regexp-tree/issues/216#issuecomment-762073297 + String.raw`/\s{0,2}/`, ], invalid: [ // Literal regex { - code: 'const foo = /\\w/ig', - errors: createError('/\\w/ig', '/\\w/gi'), - output: 'const foo = /\\w/gi', + code: String.raw`const foo = /\w/ig`, + errors: createError(String.raw`/\w/ig`, String.raw`/\w/gi`), + output: String.raw`const foo = /\w/gi`, }, { code: 'const foo = /[0-9]/', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = /\\d/', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = /\d/`, }, { code: 'const foo = /[0-9]/ig', - errors: createError('/[0-9]/ig', '/\\d/gi'), - output: 'const foo = /\\d/gi', + errors: createError('/[0-9]/ig', String.raw`/\d/gi`), + output: String.raw`const foo = /\d/gi`, }, { code: 'const foo = /[^0-9]/', - errors: createError('/[^0-9]/', '/\\D/'), - output: 'const foo = /\\D/', + errors: createError('/[^0-9]/', String.raw`/\D/`), + output: String.raw`const foo = /\D/`, }, { code: 'const foo = /[A-Za-z0-9_]/', - errors: createError('/[A-Za-z0-9_]/', '/\\w/'), - output: 'const foo = /\\w/', + errors: createError('/[A-Za-z0-9_]/', String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { - code: 'const foo = /[A-Za-z\\d_]/', - errors: createError('/[A-Za-z\\d_]/', '/\\w/'), - output: 'const foo = /\\w/', + code: String.raw`const foo = /[A-Za-z\d_]/`, + errors: createError(String.raw`/[A-Za-z\d_]/`, String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { code: 'const foo = /[a-zA-Z0-9_]/', - errors: createError('/[a-zA-Z0-9_]/', '/\\w/'), - output: 'const foo = /\\w/', + errors: createError('/[a-zA-Z0-9_]/', String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { - code: 'const foo = /[a-zA-Z\\d_]/', - errors: createError('/[a-zA-Z\\d_]/', '/\\w/'), - output: 'const foo = /\\w/', + code: String.raw`const foo = /[a-zA-Z\d_]/`, + errors: createError(String.raw`/[a-zA-Z\d_]/`, String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { - code: 'const foo = /[A-Za-z0-9_]+[0-9]?\\.[A-Za-z0-9_]*/', - errors: createError('/[A-Za-z0-9_]+[0-9]?\\.[A-Za-z0-9_]*/', '/\\w+\\d?\\.\\w*/'), - output: 'const foo = /\\w+\\d?\\.\\w*/', + code: String.raw`const foo = /[A-Za-z0-9_]+[0-9]?\.[A-Za-z0-9_]*/`, + errors: createError(String.raw`/[A-Za-z0-9_]+[0-9]?\.[A-Za-z0-9_]*/`, String.raw`/\w+\d?\.\w*/`), + output: String.raw`const foo = /\w+\d?\.\w*/`, }, { code: 'const foo = /[a-z0-9_]/i', - errors: createError('/[a-z0-9_]/i', '/\\w/i'), - output: 'const foo = /\\w/i', + errors: createError('/[a-z0-9_]/i', String.raw`/\w/i`), + output: String.raw`const foo = /\w/i`, }, { - code: 'const foo = /[a-z\\d_]/i', - errors: createError('/[a-z\\d_]/i', '/\\w/i'), - output: 'const foo = /\\w/i', + code: String.raw`const foo = /[a-z\d_]/i`, + errors: createError(String.raw`/[a-z\d_]/i`, String.raw`/\w/i`), + output: String.raw`const foo = /\w/i`, }, { code: 'const foo = /[^A-Za-z0-9_]/', - errors: createError('/[^A-Za-z0-9_]/', '/\\W/'), - output: 'const foo = /\\W/', + errors: createError('/[^A-Za-z0-9_]/', String.raw`/\W/`), + output: String.raw`const foo = /\W/`, }, { - code: 'const foo = /[^A-Za-z\\d_]/', - errors: createError('/[^A-Za-z\\d_]/', '/\\W/'), - output: 'const foo = /\\W/', + code: String.raw`const foo = /[^A-Za-z\d_]/`, + errors: createError(String.raw`/[^A-Za-z\d_]/`, String.raw`/\W/`), + output: String.raw`const foo = /\W/`, }, { code: 'const foo = /[^a-z0-9_]/i', - errors: createError('/[^a-z0-9_]/i', '/\\W/i'), - output: 'const foo = /\\W/i', + errors: createError('/[^a-z0-9_]/i', String.raw`/\W/i`), + output: String.raw`const foo = /\W/i`, }, { - code: 'const foo = /[^a-z\\d_]/i', - errors: createError('/[^a-z\\d_]/i', '/\\W/i'), - output: 'const foo = /\\W/i', + code: String.raw`const foo = /[^a-z\d_]/i`, + errors: createError(String.raw`/[^a-z\d_]/i`, String.raw`/\W/i`), + output: String.raw`const foo = /\W/i`, }, { - code: 'const foo = /[^a-z\\d_]/ig', - errors: createError('/[^a-z\\d_]/ig', '/\\W/gi'), - output: 'const foo = /\\W/gi', + code: String.raw`const foo = /[^a-z\d_]/ig`, + errors: createError(String.raw`/[^a-z\d_]/ig`, String.raw`/\W/gi`), + output: String.raw`const foo = /\W/gi`, }, { - code: 'const foo = /[^\\d_a-z]/ig', - errors: createError('/[^\\d_a-z]/ig', '/\\W/gi'), - output: 'const foo = /\\W/gi', + code: String.raw`const foo = /[^\d_a-z]/ig`, + errors: createError(String.raw`/[^\d_a-z]/ig`, String.raw`/\W/gi`), + output: String.raw`const foo = /\W/gi`, }, { code: 'const foo = /[a-z0-9_]/', - errors: createError('/[a-z0-9_]/', '/[\\d_a-z]/'), - output: 'const foo = /[\\d_a-z]/', + errors: createError('/[a-z0-9_]/', String.raw`/[\d_a-z]/`), + output: String.raw`const foo = /[\d_a-z]/`, }, { code: 'const foo = /^by @([a-zA-Z0-9-]+)/', - errors: createError('/^by @([a-zA-Z0-9-]+)/', '/^by @([\\dA-Za-z-]+)/'), - output: 'const foo = /^by @([\\dA-Za-z-]+)/', + errors: createError('/^by @([a-zA-Z0-9-]+)/', String.raw`/^by @([\dA-Za-z-]+)/`), + output: String.raw`const foo = /^by @([\dA-Za-z-]+)/`, }, { code: '/[GgHhIiΓ₯.Z:a-f"0-8%A*Γ€]/', @@ -190,115 +188,100 @@ test({ { code: '/[a0-9b]/', options: disableSortCharacterClassesOptions, - errors: createError('/[a0-9b]/', '/[a\\db]/'), - output: '/[a\\db]/', + errors: createError('/[a0-9b]/', String.raw`/[a\db]/`), + output: String.raw`/[a\db]/`, }, // `RegExp()` constructor { code: 'const foo = new RegExp(\'[0-9]\')', - errors: createError('[0-9]', '\\d'), - output: 'const foo = new RegExp(\'\\\\d\')', + errors: createError('[0-9]', String.raw`\d`), + output: String.raw`const foo = new RegExp('\\d')`, }, { code: 'const foo = new RegExp("[0-9]")', - errors: createError('[0-9]', '\\d'), - output: 'const foo = new RegExp("\\\\d")', + errors: createError('[0-9]', String.raw`\d`), + output: String.raw`const foo = new RegExp("\\d")`, }, { - code: 'const foo = new RegExp(\'\\\'[0-9]\\\'\')', - errors: createError('\'[0-9]\'', '\'\\d\''), - output: 'const foo = new RegExp(\'\\\'\\\\d\\\'\')', + code: String.raw`const foo = new RegExp('\'[0-9]\'')`, + errors: createError('\'[0-9]\'', String.raw`'\d'`), + output: String.raw`const foo = new RegExp('\'\\d\'')`, }, { code: 'const foo = new RegExp("\'[0-9]\'")', - errors: createError('\'[0-9]\'', '\'\\d\''), - output: 'const foo = new RegExp("\'\\\\d\'")', + errors: createError('\'[0-9]\'', String.raw`'\d'`), + output: String.raw`const foo = new RegExp("'\\d'")`, }, { code: 'const foo = new RegExp(\'[0-9]\', \'ig\')', - errors: createError('[0-9]', '\\d'), - output: 'const foo = new RegExp(\'\\\\d\', \'ig\')', - }, - { - code: 'const foo = new RegExp(/[0-9]/)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/)', - }, - { - code: 'const foo = new RegExp(/[0-9]/, \'ig\')', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, \'ig\')', - }, - { - code: 'const foo = new RegExp(/[0-9]/)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/)', + errors: createError('[0-9]', String.raw`\d`), + output: String.raw`const foo = new RegExp('\\d', 'ig')`, }, { code: 'const foo = new RegExp(/[0-9]/, \'ig\')', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, \'ig\')', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/, 'ig')`, }, { code: 'const foo = new RegExp(/^[^*]*[*]?$/)', - errors: createError('/^[^*]*[*]?$/', '/^[^*]*\\*?$/'), - output: 'const foo = new RegExp(/^[^*]*\\*?$/)', + errors: createError('/^[^*]*[*]?$/', String.raw`/^[^*]*\*?$/`), + output: String.raw`const foo = new RegExp(/^[^*]*\*?$/)`, }, // No `flags` { code: 'const foo = new RegExp(/[0-9]/)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/)', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/)`, }, // `flags` not `Literal` { code: 'const foo = new RegExp(/[0-9]/, ig)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, ig)', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/, ig)`, }, // `flags` not `string` { code: 'const foo = new RegExp(/[0-9]/, 0)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, 0)', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/, 0)`, }, // `\s` rewrite testCase( - '/[ \\f\\n\\r\\t\\v\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]+/', - '/\\s+/', + String.raw`/[ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/`, + String.raw`/\s+/`, ), // #499 { - code: '/^[a-z][a-z0-9\\-]{5,29}$/', - errors: createError('/^[a-z][a-z0-9\\-]{5,29}$/', '/^[a-z][\\da-z\\-]{5,29}$/'), - output: '/^[a-z][\\da-z\\-]{5,29}$/', + code: String.raw`/^[a-z][a-z0-9\-]{5,29}$/`, + errors: createError(String.raw`/^[a-z][a-z0-9\-]{5,29}$/`, String.raw`/^[a-z][\da-z\-]{5,29}$/`), + output: String.raw`/^[a-z][\da-z\-]{5,29}$/`, }, // #477 testCase( - '/[ \\n\\t\\r\\]]/g', - '/[\\t\\n\\r \\]]/g', + String.raw`/[ \n\t\r\]]/g`, + String.raw`/[\t\n\r \]]/g`, ), testCase( - '/[ \\n\\t\\r\\f"#\'()/;[\\\\\\]{}]/g', - '/[\\t\\n\\f\\r "#\'()/;[\\\\\\]{}]/g', + String.raw`/[ \n\t\r\f"#'()/;[\\\]{}]/g`, + String.raw`/[\t\n\f\r "#'()/;[\\\]{}]/g`, ), testCase( - '/[ \\n\\t\\r\\f(){}:;@!\'"\\\\\\][#]|\\/(?=\\*)/g', - '/[\\t\\n\\f\\r !"#\'():;@[\\\\\\]{}]|\\/(?=\\*)/g', + String.raw`/[ \n\t\r\f(){}:;@!'"\\\][#]|\/(?=\*)/g`, + String.raw`/[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g`, ), // #994 testCase( - '/\\s?\\s?\\s?/', - '/\\s{0,3}/', + String.raw`/\s?\s?\s?/`, + String.raw`/\s{0,3}/`, ), // Actual message { code: '/[0-9]/', - output: '/\\d/', + output: String.raw`/\d/`, errors: [ { - message: '/[0-9]/ can be optimized to /\\d/.', + message: String.raw`/[0-9]/ can be optimized to /\d/.`, }, ], }, @@ -310,17 +293,19 @@ test({ message: 'Problem parsing /(/: \n\n/(/\n ^\nUnexpected token: "/" at 1:2.', }, ], - parser: require.resolve('@typescript-eslint/parser'), + languageOptions: { + parser: parsers.typescript, + }, }, // Not fixable { code: 'const foo = /[0-9]/.toString', - errors: createError('/[0-9]/', '/\\d/'), + errors: createError('/[0-9]/', String.raw`/\d/`), }, { code: 'const foo = /[0-9]/.source', - errors: createError('/[0-9]/', '/\\d/'), + errors: createError('/[0-9]/', String.raw`/\d/`), }, ], }); diff --git a/test/catch-error-name.mjs b/test/catch-error-name.mjs index 41b8f14dd3..2c0bed9fde 100644 --- a/test/catch-error-name.mjs +++ b/test/catch-error-name.mjs @@ -29,7 +29,6 @@ function invalidTestCase(options) { test({ valid: [ - 'try {} catch (error) {}', { code: 'try {} catch (err) {}', options: [{name: 'err'}], @@ -900,12 +899,14 @@ test.typescript({ test.babel({ testerOptions: { - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: true}], - ], + languageOptions: { + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: true}], + ], + }, }, }, }, diff --git a/test/consistent-destructuring.mjs b/test/consistent-destructuring.mjs index ebe0526631..9bc1af5072 100644 --- a/test/consistent-destructuring.mjs +++ b/test/consistent-destructuring.mjs @@ -444,7 +444,7 @@ test({ b } } = foo; - console.log(foo.a.c); + console.log(foo.a.c); // 2 `, errors: [{ message: 'Use destructured variables over properties.', @@ -453,7 +453,7 @@ test({ { code: outdent` const {a} = foo; - console.log(foo.a); + console.log(foo.a); // 2 `, errors: [{ message: 'Use destructured variables over properties.', @@ -461,7 +461,7 @@ test({ desc: 'Replace `foo.a` with destructured property `a`.', output: outdent` const {a} = foo; - console.log(a); + console.log(a); // 2 `, }], }], diff --git a/test/consistent-empty-array-spread.mjs b/test/consistent-empty-array-spread.mjs new file mode 100644 index 0000000000..d8d432aa8c --- /dev/null +++ b/test/consistent-empty-array-spread.mjs @@ -0,0 +1,57 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + '[,,,]', + '[...(test ? [] : [a, b])]', + '[...(test ? [a, b] : [])]', + '[...(test ? "" : "ab")]', + '[...(test ? "ab" : "")]', + '[...(test ? "" : unknown)]', + '[...(test ? unknown : "")]', + '[...(test ? [] : unknown)]', + '[...(test ? unknown : [])]', + '_ = {...(test ? "" : [a, b])}', + '_ = {...(test ? [] : "ab")}', + 'call(...(test ? "" : [a, b]))', + 'call(...(test ? [] : "ab"))', + '[...(test ? "ab" : [a, b])]', + // Not checking + 'const EMPTY_STRING = ""; [...(test ? EMPTY_STRING : [a, b])]', + ], + invalid: [ + outdent` + [ + ...(test ? [] : "ab"), + ...(test ? "ab" : []), + ]; + `, + outdent` + const STRING = "ab"; + [ + ...(test ? [] : STRING), + ...(test ? STRING : []), + ]; + `, + outdent` + [ + ...(test ? "" : [a, b]), + ...(test ? [a, b] : ""), + ]; + `, + outdent` + const ARRAY = ["a", "b"]; + [ + /* hole */, + ...(test ? "" : ARRAY), + /* hole */, + ...(test ? ARRAY : ""), + /* hole */, + ]; + `, + '[...(foo ? "" : [])]', + ], +}); diff --git a/test/consistent-function-scoping.mjs b/test/consistent-function-scoping.mjs index 5f0ed084df..59aef7de72 100644 --- a/test/consistent-function-scoping.mjs +++ b/test/consistent-function-scoping.mjs @@ -15,9 +15,11 @@ const createError = (functionNameWithKind, loc) => ({ test({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -473,12 +475,6 @@ test({ `, errors: [createError('arrow function \'doBar\'')], }, - { - code: outdent` - const doFoo = () => bar => bar; - `, - errors: [createError('arrow function')], - }, // `this` { code: outdent` @@ -600,37 +596,86 @@ test({ // Function kinds and names, loc { code: 'function foo() { function bar() {} }', - errors: [createError('function \'bar\'', {line: 1, column: 18, endLine: 1, endColumn: 30})], + errors: [ + createError('function \'bar\'', { + line: 1, + column: 18, + endLine: 1, + endColumn: 30, + }), + ], }, { code: 'function foo() { async function bar() {} }', - errors: [createError('async function \'bar\'', {line: 1, column: 18, endLine: 1, endColumn: 36})], + errors: [ + createError('async function \'bar\'', { + line: 1, + column: 18, + endLine: 1, + endColumn: 36, + }), + ], }, { code: 'function foo() { function* bar() {} }', - errors: [createError('generator function \'bar\'', {line: 1, column: 18, endLine: 1, endColumn: 31})], + errors: [ + createError('generator function \'bar\'', { + line: 1, + column: 18, + endLine: 1, + endColumn: 31, + }), + ], }, { code: 'function foo() { async function* bar() {} }', - errors: [createError('async generator function \'bar\'', {line: 1, column: 18, endLine: 1, endColumn: 37})], + errors: [ + createError('async generator function \'bar\'', { + line: 1, + column: 18, + endLine: 1, + endColumn: 37, + }), + ], }, { code: 'function foo() { const bar = () => {} }', - errors: [createError('arrow function \'bar\'', {line: 1, column: 33, endLine: 1, endColumn: 35})], + errors: [ + createError('arrow function \'bar\'', { + line: 1, + column: 33, + endLine: 1, + endColumn: 35, + }), + ], }, { code: 'const doFoo = () => bar => bar;', - errors: [createError('arrow function', {line: 1, column: 25, endLine: 1, endColumn: 27})], + errors: [ + createError('arrow function', { + line: 1, + column: 25, + endLine: 1, + endColumn: 27, + }), + ], }, { code: 'function foo() { const bar = async () => {} }', - errors: [createError('async arrow function \'bar\'', {line: 1, column: 39, endLine: 1, endColumn: 41})], + errors: [ + createError('async arrow function \'bar\'', { + line: 1, + column: 39, + endLine: 1, + endColumn: 41, + }), + ], }, // Actual message { - code: 'function foo() { async function* bar() {} }', + code: 'function foo() { async function* baz() {} }', errors: [{ - message: 'Move async generator function \'bar\' to the outer scope.', + message: 'Move async generator function \'baz\' to the outer scope.', }], }, // React Hooks diff --git a/test/empty-brace-spaces.mjs b/test/empty-brace-spaces.mjs index f925ad7b0e..6200a92363 100644 --- a/test/empty-brace-spaces.mjs +++ b/test/empty-brace-spaces.mjs @@ -16,13 +16,12 @@ const cases = [ 'switch (foo) {case bar: {/* */}}', 'switch (foo) {default: {/* */}}', 'try {/* */} catch(foo){}', - 'try {} catch(foo){/* */}', + 'try {} catch(bar){/* */}', 'try {} catch(foo){} finally {/* */}', 'do {/* */} while (foo)', 'while (foo){/* */}', 'foo = () => {/* */}', 'foo = function (){/* */}', - 'function foo(){/* */}', 'foo = {/* */}', 'class Foo {bar() {/* */}}', 'foo = class {bar() {/* */}}', @@ -49,11 +48,11 @@ test({ ].flatMap(body => allCases.map(code => code.replace(SPACES_PLACEHOLDER, body))), // Not empty ...cases.map(code => code.replace(SPACES_PLACEHOLDER, 'unicorn')), - ...classBodyCases.map(code => code.replace(SPACES_PLACEHOLDER, 'bar() {}')), + ...classBodyCases.map(code => code.replace(SPACES_PLACEHOLDER, 'baz() {}')), // `with` { code: 'with (foo) {}', - parserOptions: {ecmaVersion: 5, sourceType: 'script'}, + languageOptions: {ecmaVersion: 5, sourceType: 'script'}, }, // We don't check these cases ...ignoredCases.map(code => code.replace(SPACES_PLACEHOLDER, ' ')), @@ -75,7 +74,7 @@ test({ code: 'with (foo) { }', output: 'with (foo) {}', errors: 1, - parserOptions: {ecmaVersion: 5, sourceType: 'script'}, + languageOptions: {ecmaVersion: 5, sourceType: 'script'}, }, ], }); @@ -94,9 +93,11 @@ test.snapshot({ }); const enableBabelPlugins = plugins => ({ - babelOptions: { - parserOpts: { - plugins, + parserOptions: { + babelOptions: { + parserOpts: { + plugins, + }, }, }, }); @@ -110,19 +111,19 @@ test.babel({ }; `, output: 'const foo = do {};', - parserOptions: enableBabelPlugin('doExpressions'), + languageOptions: enableBabelPlugin('doExpressions'), errors: 1, }, { code: 'const record = #{ };', output: 'const record = #{};', - parserOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'hash'}]), + languageOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'hash'}]), errors: 1, }, { code: 'const record = {| |};', output: 'const record = {||};', - parserOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'bar'}]), + languageOptions: enableBabelPlugin(['recordAndTuple', {syntaxType: 'bar'}]), errors: 1, }, { @@ -137,14 +138,14 @@ test.babel({ static {} } `, - parserOptions: enableBabelPlugin('classStaticBlock'), + languageOptions: enableBabelPlugin('classStaticBlock'), errors: 1, }, // ESLint can't parse this now // { // code: 'const foo = module { };', // output: 'const foo = module {};', - // parserOptions: enableBabelPlugin('moduleBlocks'), + // languageOptions: enableBabelPlugin('moduleBlocks'), // errors: 1 // }, { @@ -153,7 +154,7 @@ test.babel({ }; `, output: 'const foo = async do {};', - parserOptions: enableBabelPlugins(['doExpressions', 'asyncDoExpressions']), + languageOptions: enableBabelPlugins(['doExpressions', 'asyncDoExpressions']), errors: 1, }, ], diff --git a/test/error-message.mjs b/test/error-message.mjs index 86a2d7f0d5..f4eb5ba101 100644 --- a/test/error-message.mjs +++ b/test/error-message.mjs @@ -69,6 +69,7 @@ test.snapshot({ 'throw new Error({foo: 0}.foo)', 'throw new Error(lineNumber=2)', 'const error = new RangeError;', + 'throw Object.assign(new Error(), {foo})', ], }); diff --git a/test/escape-case.mjs b/test/escape-case.mjs index c8650853da..64f1293f2c 100644 --- a/test/escape-case.mjs +++ b/test/escape-case.mjs @@ -1,4 +1,4 @@ -/* eslint-disable no-template-curly-in-string, unicorn/escape-case */ +/* eslint-disable no-template-curly-in-string */ import {getTester} from './utils/test.mjs'; const {test} = getTester(import.meta); @@ -12,19 +12,19 @@ const errors = [ test({ valid: [ // Literal string - 'const foo = "\\xA9";', - 'const foo = "\\uD834";', - 'const foo = "\\u{1D306}";', - 'const foo = "\\uD834foo";', - 'const foo = "foo\\uD834";', - 'const foo = "foo \\uD834";', - 'const foo = "foo \\u2500";', - 'const foo = "foo \\x46";', - 'const foo = "foo\\\\xbar";', - 'const foo = "foo\\\\ubarbaz";', - 'const foo = "foo\\\\\\\\xbar";', - 'const foo = "foo\\\\\\\\ubarbaz";', - 'const foo = "\\ca";', + String.raw`const foo = "\xA9";`, + String.raw`const foo = "\uD834";`, + String.raw`const foo = "\u{1D306}";`, + String.raw`const foo = "\uD834foo";`, + String.raw`const foo = "foo\uD834";`, + String.raw`const foo = "foo \uD834";`, + String.raw`const foo = "foo \u2500";`, + String.raw`const foo = "foo \x46";`, + String.raw`const foo = "foo\\xbar";`, + String.raw`const foo = "foo\\ubarbaz";`, + String.raw`const foo = "foo\\\\xbar";`, + String.raw`const foo = "foo\\\\ubarbaz";`, + String.raw`const foo = "\ca";`, // TemplateLiteral 'const foo = `\\xA9`;', @@ -41,97 +41,98 @@ test({ 'const foo = `foo\\\\\\\\xbar`;', 'const foo = `foo\\\\\\\\ubarbaz`;', 'const foo = `\\ca`;', + 'const foo = String.raw`\\uAaAa`;', // Literal regex - 'const foo = /foo\\xA9/', - 'const foo = /foo\\uD834/', - 'const foo = /foo\\u{1D306}/u', - 'const foo = /foo\\cA/', + String.raw`const foo = /foo\xA9/`, + String.raw`const foo = /foo\uD834/`, + String.raw`const foo = /foo\u{1D306}/u`, + String.raw`const foo = /foo\cA/`, // Escape - 'const foo = /foo\\\\xa9/;', - 'const foo = /foo\\\\\\\\xa9/;', - 'const foo = /foo\\\\uD834/', - 'const foo = /foo\\\\u{1}/u', - 'const foo = /foo\\\\cA/', + String.raw`const foo = /foo\\xa9/;`, + String.raw`const foo = /foo\\\\xa9/;`, + String.raw`const foo = /foo\\uD834/`, + String.raw`const foo = /foo\\u{1}/u`, + String.raw`const foo = /foo\\cA/`, // RegExp - 'const foo = new RegExp("/\\xA9")', - 'const foo = new RegExp("/\\uD834/")', - 'const foo = new RegExp("/\\u{1D306}/", "u")', - 'const foo = new RegExp("/\\ca/")', - 'const foo = new RegExp("/\\cA/")', + String.raw`const foo = new RegExp("/\xA9")`, + String.raw`const foo = new RegExp("/\uD834/")`, + String.raw`const foo = new RegExp("/\u{1D306}/", "u")`, + String.raw`const foo = new RegExp("/\ca/")`, + String.raw`const foo = new RegExp("/\cA/")`, ], invalid: [ // Literal string { - code: 'const foo = "\\xa9";', + code: String.raw`const foo = "\xa9";`, errors, - output: 'const foo = "\\xA9";', + output: String.raw`const foo = "\xA9";`, }, // Mixed cases { - code: 'const foo = "\\xAa";', + code: String.raw`const foo = "\xAa";`, errors, - output: 'const foo = "\\xAA";', + output: String.raw`const foo = "\xAA";`, }, { - code: 'const foo = "\\uAaAa";', + code: String.raw`const foo = "\uAaAa";`, errors, - output: 'const foo = "\\uAAAA";', + output: String.raw`const foo = "\uAAAA";`, }, { - code: 'const foo = "\\u{AaAa}";', + code: String.raw`const foo = "\u{AaAa}";`, errors, - output: 'const foo = "\\u{AAAA}";', + output: String.raw`const foo = "\u{AAAA}";`, }, // Many { - code: 'const foo = "\\xAab\\xaab\\xAAb\\uAaAab\\uaaaab\\uAAAAb\\u{AaAa}b\\u{aaaa}b\\u{AAAA}";', + code: String.raw`const foo = "\xAab\xaab\xAAb\uAaAab\uaaaab\uAAAAb\u{AaAa}b\u{aaaa}b\u{AAAA}";`, errors, - output: 'const foo = "\\xAAb\\xAAb\\xAAb\\uAAAAb\\uAAAAb\\uAAAAb\\u{AAAA}b\\u{AAAA}b\\u{AAAA}";', + output: String.raw`const foo = "\xAAb\xAAb\xAAb\uAAAAb\uAAAAb\uAAAAb\u{AAAA}b\u{AAAA}b\u{AAAA}";`, }, { - code: 'const foo = "\\ud834";', + code: String.raw`const foo = "\ud834";`, errors, - output: 'const foo = "\\uD834";', + output: String.raw`const foo = "\uD834";`, }, { - code: 'const foo = "\\u{1d306}";', + code: String.raw`const foo = "\u{1d306}";`, errors, - output: 'const foo = "\\u{1D306}";', + output: String.raw`const foo = "\u{1D306}";`, }, { - code: 'const foo = "\\ud834foo";', + code: String.raw`const foo = "\ud834foo";`, errors, - output: 'const foo = "\\uD834foo";', + output: String.raw`const foo = "\uD834foo";`, }, { - code: 'const foo = "foo\\ud834";', + code: String.raw`const foo = "foo\ud834";`, errors, - output: 'const foo = "foo\\uD834";', + output: String.raw`const foo = "foo\uD834";`, }, { - code: 'const foo = "foo \\ud834";', + code: String.raw`const foo = "foo \ud834";`, errors, - output: 'const foo = "foo \\uD834";', + output: String.raw`const foo = "foo \uD834";`, }, { - code: 'const foo = "\\\\\\ud834foo";', + code: String.raw`const foo = "\\\ud834foo";`, errors, - output: 'const foo = "\\\\\\uD834foo";', + output: String.raw`const foo = "\\\uD834foo";`, }, { - code: 'const foo = "foo\\\\\\ud834";', + code: String.raw`const foo = "foo\\\ud834";`, errors, - output: 'const foo = "foo\\\\\\uD834";', + output: String.raw`const foo = "foo\\\uD834";`, }, { - code: 'const foo = "foo \\\\\\ud834";', + code: String.raw`const foo = "foo \\\ud834";`, errors, - output: 'const foo = "foo \\\\\\uD834";', + output: String.raw`const foo = "foo \\\uD834";`, }, // TemplateLiteral @@ -190,6 +191,18 @@ test({ errors, output: 'const foo = `foo \\\\\\uD834`;', }, + // TODO: This is not safe, it will be broken if `tagged` uses `arguments[0].raw` + // #2341 + { + code: 'const foo = tagged`\\uAaAa`;', + errors, + output: 'const foo = tagged`\\uAAAA`;', + }, + { + code: 'const foo = `\\uAaAa```;', + errors, + output: 'const foo = `\\uAAAA```;', + }, // Mixed cases { @@ -217,75 +230,75 @@ test({ // Literal regex { - code: 'const foo = /\\xa9/;', + code: String.raw`const foo = /\xa9/;`, errors, - output: 'const foo = /\\xA9/;', + output: String.raw`const foo = /\xA9/;`, }, { - code: 'const foo = /\\ud834/', + code: String.raw`const foo = /\ud834/`, errors, - output: 'const foo = /\\uD834/', + output: String.raw`const foo = /\uD834/`, }, { - code: 'const foo = /\\u{1d306}/u', + code: String.raw`const foo = /\u{1d306}/u`, errors, - output: 'const foo = /\\u{1D306}/u', + output: String.raw`const foo = /\u{1D306}/u`, }, { - code: 'const foo = /\\ca/', + code: String.raw`const foo = /\ca/`, errors, - output: 'const foo = /\\cA/', + output: String.raw`const foo = /\cA/`, }, { - code: 'const foo = /foo\\\\\\xa9/;', + code: String.raw`const foo = /foo\\\xa9/;`, errors, - output: 'const foo = /foo\\\\\\xA9/;', + output: String.raw`const foo = /foo\\\xA9/;`, }, { - code: 'const foo = /foo\\\\\\\\\\xa9/;', + code: String.raw`const foo = /foo\\\\\xa9/;`, errors, - output: 'const foo = /foo\\\\\\\\\\xA9/;', + output: String.raw`const foo = /foo\\\\\xA9/;`, }, // Mixed cases { - code: 'const foo = /\\xAa/;', + code: String.raw`const foo = /\xAa/;`, errors, - output: 'const foo = /\\xAA/;', + output: String.raw`const foo = /\xAA/;`, }, { - code: 'const foo = /\\uAaAa/;', + code: String.raw`const foo = /\uAaAa/;`, errors, - output: 'const foo = /\\uAAAA/;', + output: String.raw`const foo = /\uAAAA/;`, }, { - code: 'const foo = /\\u{AaAa}/;', + code: String.raw`const foo = /\u{AaAa}/;`, errors, - output: 'const foo = /\\u{AAAA}/;', + output: String.raw`const foo = /\u{AAAA}/;`, }, // Many { - code: 'const foo = /\\xAab\\xaab\\xAAb\\uAaAab\\uaaaab\\uAAAAb\\u{AaAa}b\\u{aaaa}b\\u{AAAA}b\\ca/;', + code: String.raw`const foo = /\xAab\xaab\xAAb\uAaAab\uaaaab\uAAAAb\u{AaAa}b\u{aaaa}b\u{AAAA}b\ca/;`, errors, - output: 'const foo = /\\xAAb\\xAAb\\xAAb\\uAAAAb\\uAAAAb\\uAAAAb\\u{AAAA}b\\u{AAAA}b\\u{AAAA}b\\cA/;', + output: String.raw`const foo = /\xAAb\xAAb\xAAb\uAAAAb\uAAAAb\uAAAAb\u{AAAA}b\u{AAAA}b\u{AAAA}b\cA/;`, }, // RegExp { - code: 'const foo = new RegExp("/\\xa9")', + code: String.raw`const foo = new RegExp("/\xa9")`, errors, - output: 'const foo = new RegExp("/\\xA9")', + output: String.raw`const foo = new RegExp("/\xA9")`, }, { - code: 'const foo = new RegExp("/\\ud834/")', + code: String.raw`const foo = new RegExp("/\ud834/")`, errors, - output: 'const foo = new RegExp("/\\uD834/")', + output: String.raw`const foo = new RegExp("/\uD834/")`, }, { - code: 'const foo = new RegExp("/\\u{1d306}/", "u")', + code: String.raw`const foo = new RegExp("/\u{1d306}/", "u")`, errors, - output: 'const foo = new RegExp("/\\u{1D306}/", "u")', + output: String.raw`const foo = new RegExp("/\u{1D306}/", "u")`, }, ], }); diff --git a/test/expiring-todo-comments.mjs b/test/expiring-todo-comments.mjs index 1ddfaf595b..9174b5f5ad 100644 --- a/test/expiring-todo-comments.mjs +++ b/test/expiring-todo-comments.mjs @@ -96,15 +96,15 @@ test({ }, { code: '// TODO ISSUE-123 fix later', - options: [{allowWarningComments: false, ignore: ['ISSUE-\\d+']}], + options: [{allowWarningComments: false, ignore: [String.raw`ISSUE-\d+`]}], }, { code: '// TODO [ISSUE-123] fix later', - options: [{allowWarningComments: false, ignore: ['ISSUE-\\d+']}], + options: [{allowWarningComments: false, ignore: [String.raw`ISSUE-\d+`]}], }, { code: '// TODO [1999-01-01, ISSUE-123] fix later', - options: [{allowWarningComments: false, ignore: ['ISSUE-\\d+']}], + options: [{allowWarningComments: false, ignore: [String.raw`ISSUE-\d+`]}], }, { code: '// TODO [Issue-123] fix later', diff --git a/test/explicit-length-check.mjs b/test/explicit-length-check.mjs index f923935aa1..6dbd49558d 100644 --- a/test/explicit-length-check.mjs +++ b/test/explicit-length-check.mjs @@ -3,20 +3,18 @@ import {getTester, parsers} from './utils/test.mjs'; const {test} = getTester(import.meta); -const suggestionCase = ({code, output, desc, options = []}) => { - const suggestion = {output}; - if (desc) { - suggestion.desc = desc; - } +const TYPE_NON_ZERO = 'non-zero'; - return { - code, - options, - errors: [ - {suggestions: [suggestion]}, - ], - }; -}; +const suggestionCase = ({code, messageId, output, desc, options = []}) => ({ + code, + options, + errors: [ + { + messageId, + suggestions: [{desc, output}], + }, + ], +}); const nonZeroCases = [ 'foo.length', @@ -110,37 +108,46 @@ test({ invalid: [ suggestionCase({ code: 'const x = foo.length || bar()', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length > 0 || bar()', desc: 'Replace `.length` with `.length > 0`.', }), suggestionCase({ code: 'const x = foo.length || unknown', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length > 0 || unknown', desc: 'Replace `.length` with `.length > 0`.', }), suggestionCase({ code: 'const NON_NUMBER = "2"; const x = foo.length || NON_NUMBER', + messageId: TYPE_NON_ZERO, output: 'const NON_NUMBER = "2"; const x = foo.length > 0 || NON_NUMBER', desc: 'Replace `.length` with `.length > 0`.', }), suggestionCase({ code: 'const x = foo.length || bar()', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length !== 0 || bar()', desc: 'Replace `.length` with `.length !== 0`.', options: [{'non-zero': 'not-equal'}], }), suggestionCase({ code: 'const x = foo.length || bar()', + messageId: TYPE_NON_ZERO, output: 'const x = foo.length > 0 || bar()', desc: 'Replace `.length` with `.length > 0`.', options: [{'non-zero': 'greater-than'}], }), suggestionCase({ code: '() => foo.length && bar()', + messageId: TYPE_NON_ZERO, + desc: 'Replace `.length` with `.length > 0`.', output: '() => foo.length > 0 && bar()', }), suggestionCase({ code: 'alert(foo.length && bar())', + messageId: TYPE_NON_ZERO, + desc: 'Replace `.length` with `.length > 0`.', output: 'alert(foo.length > 0 && bar())', }), ], @@ -223,7 +230,9 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.vue, + languageOptions: { + parser: parsers.vue, + }, }, valid: [ '
', diff --git a/test/filename-case.mjs b/test/filename-case.mjs index 26c3969b83..012feff0d2 100644 --- a/test/filename-case.mjs +++ b/test/filename-case.mjs @@ -19,9 +19,8 @@ function testManyCases(filename, chosenCases, errorMessage) { } function testCaseWithOptions(filename, errorMessage, options = []) { - return { - code: `/* Filename ${filename} */`, - filename, + const testCase = { + code: `/* Filename: ${filename} */`, options, errors: errorMessage && [ { @@ -29,6 +28,12 @@ function testCaseWithOptions(filename, errorMessage, options = []) { }, ], }; + + if (filename !== undefined) { + testCase.filename = filename; + } + + return testCase; } test({ @@ -97,52 +102,52 @@ test({ testCase('src/foo/[fooBar].js', 'camelCase'), testCase('src/foo/{foo_bar}.js', 'snakeCase'), testCaseWithOptions(undefined, undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions(undefined, undefined, [ {case: 'kebabCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/index.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/index.js', undefined, [ {case: 'kebabCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'kebabCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'camelCase', ignore: ['FOOBAR\\.js']}, + {case: 'camelCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'camelCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'snakeCase', ignore: ['FOOBAR\\.js']}, + {case: 'snakeCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'pascalCase', ignore: ['FOOBAR\\.js']}, + {case: 'pascalCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'pascalCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js', 'BARBAZ\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`, String.raw`BARBAZ\.js`]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js', /BARBAZ\.js/u]}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`, /BARBAZ\.js/u]}, ]), testCaseWithOptions('src/foo/[FOOBAR].js', undefined, [ - {case: 'camelCase', ignore: ['\\[FOOBAR\\]\\.js']}, + {case: 'camelCase', ignore: [String.raw`\[FOOBAR\]\.js`]}, ]), testCaseWithOptions('src/foo/[FOOBAR].js', undefined, [ {case: 'camelCase', ignore: [/\[FOOBAR]\.js/]}, ]), testCaseWithOptions('src/foo/{FOOBAR}.js', undefined, [ - {case: 'snakeCase', ignore: ['\\{FOOBAR\\}\\.js']}, + {case: 'snakeCase', ignore: [String.raw`\{FOOBAR\}\.js`]}, ]), testCaseWithOptions('src/foo/{FOOBAR}.js', undefined, [ {case: 'snakeCase', ignore: [/{FOOBAR}\.js/]}, @@ -166,16 +171,16 @@ test({ {case: 'kebabCase', ignore: [/foo/iu]}, ]), testCaseWithOptions('src/foo/foo-bar.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.web.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.android.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.ios.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.something.js', undefined, [ {case: 'kebabCase', ignore: [/\.(?:web|android|ios|something)\.js$/u]}, @@ -187,13 +192,13 @@ test({ {case: 'kebabCase', ignore: [/^[Ff]oo/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'kebabCase', ignore: ['^FOO', 'BAZ\\.js$']}, + {case: 'kebabCase', ignore: ['^FOO', String.raw`BAZ\.js$`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'kebabCase', ignore: [/^FOO/, /BAZ\.js$/u]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ - {case: 'kebabCase', ignore: ['^FOO', 'BAZ\\.js$']}, + {case: 'kebabCase', ignore: ['^FOO', String.raw`BAZ\.js$`]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ {case: 'kebabCase', ignore: [/^FOO/, /BAZ\.js$/]}, @@ -206,7 +211,7 @@ test({ snakeCase: true, pascalCase: true, }, - ignore: ['FOOBAR\\.js'], + ignore: [String.raw`FOOBAR\.js`], }, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ @@ -228,7 +233,7 @@ test({ snakeCase: true, pascalCase: true, }, - ignore: ['FOOBAR\\.js', 'BaRbAz\\.js'], + ignore: [String.raw`FOOBAR\.js`, String.raw`BaRbAz\.js`], }, ]), testCaseWithOptions('src/foo/BaRbAz.js', undefined, [ @@ -434,12 +439,12 @@ test({ testCaseWithOptions( 'src/foo/barBaz.js', 'Filename is not in kebab case. Rename it to `bar-baz.js`.', - [{case: 'kebabCase', ignore: ['FOOBAR\\.js']}], + [{case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}], ), testCaseWithOptions( 'src/foo/barBaz.js', 'Filename is not in kebab case. Rename it to `bar-baz.js`.', - [{case: 'kebabCase', ignore: ['/FOOBAR\\.js/']}], + [{case: 'kebabCase', ignore: [String.raw`/FOOBAR\.js/`]}], ), testCaseWithOptions( 'src/foo/barBaz.js', @@ -449,7 +454,7 @@ test({ testCaseWithOptions( 'src/foo/fooBar.js', 'Filename is not in kebab case. Rename it to `foo-bar.js`.', - [{case: 'kebabCase', ignore: ['FOOBAR\\.js']}], + [{case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}], ), testCaseWithOptions( 'src/foo/fooBar.js', @@ -459,7 +464,7 @@ test({ testCaseWithOptions( 'src/foo/fooBar.js', 'Filename is not in kebab case. Rename it to `foo-bar.js`.', - [{case: 'kebabCase', ignore: ['FOOBAR\\.js', 'foobar\\.js']}], + [{case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`, String.raw`foobar\.js`]}], ), testCaseWithOptions( 'src/foo/fooBar.js', @@ -475,7 +480,7 @@ test({ camelCase: true, snakeCase: true, }, - ignore: ['FOOBAR\\.js'], + ignore: [String.raw`FOOBAR\.js`], }, ], ), @@ -501,7 +506,7 @@ test({ camelCase: true, snakeCase: true, }, - ignore: ['BaRbAz\\.js'], + ignore: [String.raw`BaRbAz\.js`], }, ], ), diff --git a/test/import-style.mjs b/test/import-style.mjs index cd0a821c1b..7ed675d94c 100644 --- a/test/import-style.mjs +++ b/test/import-style.mjs @@ -141,14 +141,6 @@ test({ code: 'import {default as chalk} from \'chalk\'', options: [], }, - { - code: 'const {inspect} = require(\'util\')', - options: [], - }, - { - code: 'const {inspect} = require(\'node:util\')', - options: [], - }, { code: 'export {promisify, callbackify} from \'util\'', options: [], @@ -260,10 +252,6 @@ test({ `, errors: [unassignedError], }, - { - code: 'const {x} = require(\'unassigned\')', - errors: [unassignedError], - }, { code: 'const {x: y} = require(\'unassigned\')', errors: [unassignedError], @@ -552,62 +540,62 @@ test({ { code: 'import util from \'util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import util from \'node:util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import * as util from \'util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import * as util from \'node:util\'', options: [], - errors: [{}], + errors: 1, }, { code: 'const util = require(\'util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'const util = require(\'node:util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'node:util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'ut\' + \'il\')', options: [], - errors: [{}], + errors: 1, }, { code: 'require(\'node:\' + \'util\')', options: [], - errors: [{}], + errors: 1, }, { code: 'import {red} from \'chalk\'', options: [], - errors: [{}], + errors: 1, }, { code: 'import {red as green} from \'chalk\'', options: [], - errors: [{}], + errors: 1, }, { code: outdent` @@ -616,7 +604,7 @@ test({ } `, options: [], - errors: [{}], + errors: 1, }, { diff --git a/test/integration/run-eslint.mjs b/test/integration/run-eslint.mjs index d49a19f2da..57a44bf017 100644 --- a/test/integration/run-eslint.mjs +++ b/test/integration/run-eslint.mjs @@ -152,7 +152,7 @@ async function runEslint(project) { - warning: ${chalk.gray(warningCount)} - fixable error: ${chalk.gray(fixableErrorCount)} - fixable warning: ${chalk.gray(fixableWarningCount)} - - duration: ${chalk.gray(prettyMilliseconds(Number((process.hrtime.bigint() - startTime) / 1_000_000n)))} + - duration: ${chalk.gray(prettyMilliseconds((process.hrtime.bigint() - startTime) / 1_000_000n))} `); } diff --git a/test/new-for-builtins.mjs b/test/new-for-builtins.mjs index 6ebc585371..f737fc28e1 100644 --- a/test/new-for-builtins.mjs +++ b/test/new-for-builtins.mjs @@ -101,7 +101,7 @@ test.snapshot({ { code: 'new Symbol("")', - globals: {Symbol: 'off'}, + languageOptions: {globals: {Symbol: 'off'}}, }, ], invalid: [ @@ -190,14 +190,14 @@ test.snapshot({ `, { code: 'globalThis.Array()', - globals: {Array: 'off'}, + languageOptions: {globals: {Array: 'off'}}, }, { code: outdent` const {Array} = globalThis; Array(); `, - globals: {Array: 'off'}, + languageOptions: {globals: {Symbol: 'off'}}, }, 'const foo = Object()', 'const foo = Array()', @@ -231,26 +231,31 @@ test.snapshot({ 'const foo = new Number(\'123\')', 'const foo = new String()', 'const foo = new Symbol()', - ` - function varCheck() { - { - var WeakMap = function() {}; - } - // This should not reported - return WeakMap() + outdent` + function varCheck() { + { + var WeakMap = function() {}; } - function constCheck() { - { - const Array = function() {}; - } - return Array() + // This should not reported + return WeakMap() + } + function constCheck() { + { + const Array = function() {}; } - function letCheck() { - { - let Map = function() {}; - } - return Map() + return Array() + } + function letCheck() { + { + let Map = function() {}; } - `, + return Map() + } + `, + outdent` + function foo() { + return(globalThis).Map() + } + `, ], }); diff --git a/test/no-abusive-eslint-disable.mjs b/test/no-abusive-eslint-disable.mjs index 111450c748..96bc0db7fa 100644 --- a/test/no-abusive-eslint-disable.mjs +++ b/test/no-abusive-eslint-disable.mjs @@ -4,16 +4,12 @@ import {getTester} from './utils/test.mjs'; const {test} = getTester(import.meta); test({ - beforeAll(tester) { - // Define rules for test - for (const rule of [ - 'plugin/rule', - '@scope/plugin/rule-name', - '@scope/rule-name', - '@scopewithoutplugin', - ]) { - tester.linter.defineRule(rule, {}); - } + testerOptions: { + plugins: Object.fromEntries([ + ['plugin-name', 'rule-name'], + ['@scope/plugin', 'rule-name'], + ['@scope', 'rule-name'], + ].map(([pluginName, ruleName]) => [pluginName, {rules: {[ruleName]: {}}}])), }, valid: [ 'eval();', @@ -23,7 +19,7 @@ test({ 'eval(); // eslint-disable-line no-eval', 'eval(); //\teslint-disable-line no-eval', 'eval(); /* eslint-disable-line no-eval */', - 'eval(); // eslint-disable-line plugin/rule', + 'eval(); // eslint-disable-line plugin-name/rule-name', 'eval(); // eslint-disable-line @scope/plugin/rule-name', 'eval(); // eslint-disable-line no-eval, @scope/plugin/rule-name', 'eval(); // eslint-disable-line @scope/rule-name', @@ -31,10 +27,11 @@ test({ 'eval(); // eslint-line-disable', 'eval(); // some comment', '/* eslint-disable no-eval */', - outdent` - /* eslint-disable no-abusive-eslint-disable */ - eval(); // eslint-disable-line - `, + // TODO[@fisker]: Figure out how to test this + // outdent` + // /* eslint-disable no-abusive-eslint-disable */ + // eval(); // eslint-disable-line + // `, outdent` foo(); // eslint-disable-line no-eval @@ -51,15 +48,7 @@ test({ eval(); `, ], - invalid: [ - { - code: outdent` - // eslint-disable-next-line @scopewithoutplugin - eval(); - `, - errors: 1, - }, - ], + invalid: [], }); test.snapshot({ diff --git a/test/no-anonymous-default-export.mjs b/test/no-anonymous-default-export.mjs index c9be26d50d..f60bf32d2e 100644 --- a/test/no-anonymous-default-export.mjs +++ b/test/no-anonymous-default-export.mjs @@ -269,25 +269,29 @@ test.snapshot({ // Decorators const decoratorsBeforeExportOptions = { - parser: parsers.babel, - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: true}], - ], + languageOptions: { + parser: parsers.babel, + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: true}], + ], + }, }, }, }, }; const decoratorsAfterExportOptions = { - parser: parsers.babel, - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: false}], - ], + languageOptions: { + parser: parsers.babel, + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: false}], + ], + }, }, }, }, diff --git a/test/no-array-callback-reference.mjs b/test/no-array-callback-reference.mjs index a9f3bd9e97..bca779d286 100644 --- a/test/no-array-callback-reference.mjs +++ b/test/no-array-callback-reference.mjs @@ -6,6 +6,8 @@ const {test} = getTester(import.meta); const ERROR_WITH_NAME_MESSAGE_ID = 'error-with-name'; const ERROR_WITHOUT_NAME_MESSAGE_ID = 'error-without-name'; +const REPLACE_WITH_NAME_MESSAGE_ID = 'replace-with-name'; +const REPLACE_WITHOUT_NAME_MESSAGE_ID = 'replace-without-name'; const simpleMethods = [ 'every', @@ -35,7 +37,8 @@ const generateError = (method, name) => ({ }); // Only test output is good enough -const suggestionOutput = output => ({ +const suggestionOutput = (output, name) => ({ + messageId: name ? REPLACE_WITH_NAME_MESSAGE_ID : REPLACE_WITHOUT_NAME_MESSAGE_ID, output, }); @@ -44,7 +47,7 @@ const invalidTestCase = (({code, method, name, suggestions}) => ({ errors: [ { ...generateError(method, name), - suggestions: suggestions.map(output => suggestionOutput(output)), + suggestions: suggestions.map(output => suggestionOutput(output, name)), }, ], })); @@ -278,27 +281,45 @@ test({ // Actual messages { - code: 'foo.map(fn)', + code: 'bar.map(fn)', errors: [ { message: 'Do not pass function `fn` directly to `.map(…)`.', suggestions: [ - {desc: 'Replace function `fn` with `… => fn(element)`.'}, - {desc: 'Replace function `fn` with `… => fn(element, index)`.'}, - {desc: 'Replace function `fn` with `… => fn(element, index, array)`.'}, + { + desc: 'Replace function `fn` with `… => fn(element)`.', + output: 'bar.map((element) => fn(element))', + }, + { + desc: 'Replace function `fn` with `… => fn(element, index)`.', + output: 'bar.map((element, index) => fn(element, index))', + }, + { + desc: 'Replace function `fn` with `… => fn(element, index, array)`.', + output: 'bar.map((element, index, array) => fn(element, index, array))', + }, ], }, ], }, { - code: 'foo.reduce(fn)', + code: 'bar.reduce(fn)', errors: [ { message: 'Do not pass function `fn` directly to `.reduce(…)`.', suggestions: [ - {desc: 'Replace function `fn` with `… => fn(accumulator, element)`.'}, - {desc: 'Replace function `fn` with `… => fn(accumulator, element, index)`.'}, - {desc: 'Replace function `fn` with `… => fn(accumulator, element, index, array)`.'}, + { + desc: 'Replace function `fn` with `… => fn(accumulator, element)`.', + output: 'bar.reduce((accumulator, element) => fn(accumulator, element))', + }, + { + desc: 'Replace function `fn` with `… => fn(accumulator, element, index)`.', + output: 'bar.reduce((accumulator, element, index) => fn(accumulator, element, index))', + }, + { + desc: 'Replace function `fn` with `… => fn(accumulator, element, index, array)`.', + output: 'bar.reduce((accumulator, element, index, array) => fn(accumulator, element, index, array))', + }, ], }, ], @@ -309,9 +330,18 @@ test({ { message: 'Do not pass function directly to `.map(…)`.', suggestions: [ - {desc: 'Replace function with `… => …(element)`.'}, - {desc: 'Replace function with `… => …(element, index)`.'}, - {desc: 'Replace function with `… => …(element, index, array)`.'}, + { + desc: 'Replace function with `… => …(element)`.', + output: 'foo.map((element) => lib.fn(element))', + }, + { + desc: 'Replace function with `… => …(element, index)`.', + output: 'foo.map((element, index) => lib.fn(element, index))', + }, + { + desc: 'Replace function with `… => …(element, index, array)`.', + output: 'foo.map((element, index, array) => lib.fn(element, index, array))', + }, ], }, ], @@ -322,9 +352,18 @@ test({ { message: 'Do not pass function directly to `.reduce(…)`.', suggestions: [ - {desc: 'Replace function with `… => …(accumulator, element)`.'}, - {desc: 'Replace function with `… => …(accumulator, element, index)`.'}, - {desc: 'Replace function with `… => …(accumulator, element, index, array)`.'}, + { + desc: 'Replace function with `… => …(accumulator, element)`.', + output: 'foo.reduce((accumulator, element) => lib.fn(accumulator, element))', + }, + { + desc: 'Replace function with `… => …(accumulator, element, index)`.', + output: 'foo.reduce((accumulator, element, index) => lib.fn(accumulator, element, index))', + }, + { + desc: 'Replace function with `… => …(accumulator, element, index, array)`.', + output: 'foo.reduce((accumulator, element, index, array) => lib.fn(accumulator, element, index, array))', + }, ], }, ], diff --git a/test/no-array-for-each.mjs b/test/no-array-for-each.mjs index 1d16ba62cc..e5f494f945 100644 --- a/test/no-array-for-each.mjs +++ b/test/no-array-for-each.mjs @@ -547,21 +547,21 @@ test({ } `, errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', }, }, { code: 'foo.forEach(function(element, element) {})', errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', }, }, { code: 'foo.forEach(function element(element, element) {})', errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', }, }, @@ -622,21 +622,22 @@ test.typescript({ ], }); -const globalReturnOptions = { - sourceType: 'script', - ecmaFeatures: { - globalReturn: true, - }, -}; test({ - valid: [ - { - code: outdent` - foo.notForEach(element => bar(element)); - while (true) return; - `, - parserOptions: globalReturnOptions, + testerOptions: { + languageOptions: { + sourceType: 'script', + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, + }, }, + }, + valid: [ + outdent` + foo.notForEach(element => bar(element)); + while (true) return; + `, ], invalid: [ { @@ -649,7 +650,6 @@ test({ for (const element of foo) bar(element); `, errors: 1, - parserOptions: globalReturnOptions, }, { code: outdent` @@ -660,12 +660,10 @@ test({ while (true) return; `, errors: 1, - parserOptions: globalReturnOptions, }, { code: 'return foo.forEach(element => {bar(element)});', errors: 1, - parserOptions: globalReturnOptions, }, { code: outdent` @@ -679,7 +677,6 @@ test({ } `, errors: 1, - parserOptions: globalReturnOptions, }, ], }); diff --git a/test/no-array-method-this-argument.mjs b/test/no-array-method-this-argument.mjs index 6cb708b5c9..9d5feba658 100644 --- a/test/no-array-method-this-argument.mjs +++ b/test/no-array-method-this-argument.mjs @@ -14,6 +14,10 @@ test.snapshot({ 'Array.from?.(iterableOrArrayLike, () => {}, thisArgument)', 'Array?.from(iterableOrArrayLike, () => {}, thisArgument)', 'NotArray.from(iterableOrArrayLike, () => {}, thisArgument)', + 'new Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument)', + 'Array.fromAsync?.(iterableOrArrayLike, () => {}, thisArgument)', + 'Array?.fromAsync(iterableOrArrayLike, () => {}, thisArgument)', + 'NotArray.fromAsync(iterableOrArrayLike, () => {}, thisArgument)', // More or less arguments 'array.map()', @@ -28,6 +32,13 @@ test.snapshot({ 'Array.from(iterableOrArrayLike, ...() => {}, thisArgument)', 'Array.from(...iterableOrArrayLike, () => {}, thisArgument)', 'Array.from(iterableOrArrayLike, () => {}, thisArgument, extraArgument)', + 'Array.fromAsync()', + 'Array.fromAsync(iterableOrArrayLike)', + 'Array.fromAsync(iterableOrArrayLike, () => {},)', + 'Array.fromAsync(iterableOrArrayLike, () => {}, ...thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, ...() => {}, thisArgument)', + 'Array.fromAsync(...iterableOrArrayLike, () => {}, thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument, extraArgument)', // Ignored 'lodash.every(array, () => {})', @@ -52,8 +63,11 @@ test.snapshot({ 'array.map(1, thisArgument)', 'async () => array.map(await callback, thisArgument)', 'Array.from(iterableOrArrayLike, new Callback, thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, new Callback, thisArgument)', 'Array.from(iterableOrArrayLike, 1, thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, 1, thisArgument)', 'Array.from(iterableOrArrayLike, await callback, thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, await callback, thisArgument)', ], invalid: [ 'array.every(() => {}, thisArgument)', @@ -66,13 +80,16 @@ test.snapshot({ 'array.forEach(() => {}, thisArgument)', 'array.map(() => {}, thisArgument)', 'Array.from(iterableOrArrayLike, () => {}, thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument)', // Comma 'array.map(() => {}, thisArgument,)', 'array.map(() => {}, (0, thisArgument),)', 'Array.from(iterableOrArrayLike, () => {}, thisArgument,)', + 'Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument,)', // Side effect 'array.map(() => {}, thisArgumentHasSideEffect())', 'Array.from(iterableOrArrayLike, () => {}, thisArgumentHasSideEffect())', + 'Array.fromAsync(iterableOrArrayLike, () => {}, thisArgumentHasSideEffect())', ], }); @@ -82,35 +99,49 @@ test.snapshot({ invalid: [ 'array.map(callback, thisArgument)', 'Array.from(iterableOrArrayLike, callback, thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, callback, thisArgument)', 'array.map(callback, (0, thisArgument))', 'Array.from(iterableOrArrayLike, callback, (0, thisArgument))', + 'Array.fromAsync(iterableOrArrayLike, callback, (0, thisArgument))', 'array.map(function () {}, thisArgument)', 'Array.from(iterableOrArrayLike, function () {}, thisArgument)', 'array.map(function callback () {}, thisArgument)', 'Array.from(iterableOrArrayLike, function callback () {}, thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, function callback () {}, thisArgument)', { code: 'array.map( foo as bar, (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, { code: 'Array.from(iterableOrArrayLike, foo as bar, (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, + }, + { + code: 'Array.fromAsync(iterableOrArrayLike, foo as bar, (( thisArgument )),)', + languageOptions: {parser: parsers.typescript}, }, { code: 'array.map( (( foo as bar )), (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, { code: 'Array.from(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),)', - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, + }, + { + code: 'Array.fromAsync(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),)', + languageOptions: {parser: parsers.typescript}, }, 'array.map( (( 0, callback )), (( thisArgument )),)', 'Array.from(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),)', + 'Array.fromAsync(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),)', // This callback is actually arrow function, but we don't know 'array.map((0, () => {}), thisArgument)', 'Array.from(iterableOrArrayLike, (0, () => {}), thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, (0, () => {}), thisArgument)', // This callback is a bound function, but we don't know 'array.map(callback.bind(foo), thisArgument)', 'Array.from(iterableOrArrayLike, callback.bind(foo), thisArgument)', + 'Array.fromAsync(iterableOrArrayLike, callback.bind(foo), thisArgument)', ], }); diff --git a/test/no-array-reduce.mjs b/test/no-array-reduce.mjs index 6b370522c0..9b425a4ee4 100644 --- a/test/no-array-reduce.mjs +++ b/test/no-array-reduce.mjs @@ -30,7 +30,7 @@ test({ // Computed 'foo[reduce](fn);', // Not listed method or property - 'foo.notListed(fn);', + 'foo.notListed(fn);// reduce', // More or less argument(s) 'foo.reduce();', 'foo.reduce(fn, extraArgument1, extraArgument2);', @@ -40,7 +40,7 @@ test({ // Not `CallExpression` 'new [].reduce.call(foo, fn);', // Not `MemberExpression` - 'call(foo, fn);', + 'call(foo, fn);// reduce', 'reduce.call(foo, fn);', // `callee.property` is not a `Identifier` '[].reduce["call"](foo, fn);', @@ -50,7 +50,7 @@ test({ '[][reduce].call(foo, fn);', // Not listed method or property '[].reduce.notListed(foo, fn);', - '[].notListed.call(foo, fn);', + '[].notListed.call(foo, fn);// reduce', // Not empty '[1].reduce.call(foo, fn)', // Not ArrayExpression @@ -61,9 +61,6 @@ test({ // Test `Array.prototype.{call,apply}` // Not `CallExpression` 'new Array.prototype.reduce.call(foo, fn);', - // Not `MemberExpression` - 'call(foo, fn);', - 'reduce.call(foo, fn);', // `callee.property` is not a `Identifier` 'Array.prototype.reduce["call"](foo, fn);', 'Array.prototype["reduce"].call(foo, fn);', @@ -75,7 +72,7 @@ test({ 'Array[prototype].reduce.call(foo, fn);', // Not listed method 'Array.prototype.reduce.notListed(foo, fn);', - 'Array.prototype.notListed.call(foo, fn);', + 'Array.prototype.notListed.call(foo, fn);// reduce', 'Array.notListed.reduce.call(foo, fn);', // Not `Array` 'NotArray.prototype.reduce.call(foo, fn);', diff --git a/test/no-console-spaces.mjs b/test/no-console-spaces.mjs index ce8f692b97..1e9f220185 100644 --- a/test/no-console-spaces.mjs +++ b/test/no-console-spaces.mjs @@ -16,7 +16,6 @@ test({ 'console.log("abc", "def");', 'console.log(\'abc\', "def");', 'console.log(`abc`, "def");', - 'console.log("abc", "def");', 'console.log(`\nabc\ndef\n`);', 'console.log(\' \', "def");', @@ -31,8 +30,8 @@ test({ 'console.log(\' \', "def");', 'console.log("abc ", "def");', - 'console.log("abc\\t", "def");', - 'console.log("abc\\n", "def");', + String.raw`console.log("abc\t", "def");`, + String.raw`console.log("abc\n", "def");`, 'console.log(" abc", "def");', 'console.log(" abc", "def");', diff --git a/test/no-for-loop.mjs b/test/no-for-loop.mjs index 424b73ad62..38107887c5 100644 --- a/test/no-for-loop.mjs +++ b/test/no-for-loop.mjs @@ -730,7 +730,7 @@ test({ test(avoidTestTitleConflict({ testerOptions: { - parserOptions: { + languageOptions: { sourceType: 'script', ecmaVersion: 5, }, diff --git a/test/no-hex-escape.mjs b/test/no-hex-escape.mjs index 46c4eaa03f..9d3d1400f4 100644 --- a/test/no-hex-escape.mjs +++ b/test/no-hex-escape.mjs @@ -9,19 +9,19 @@ const error = { const tests = { valid: [ 'const foo = \'foo\'', - 'const foo = \'\\u00b1\'', - 'const foo = \'\\u00b1\\u00b1\'', - 'const foo = \'foo\\u00b1\'', - 'const foo = \'foo\\u00b1foo\'', - 'const foo = \'\\u00b1foo\'', - 'const foo = \'\\\\xb1\'', - 'const foo = \'\\\\\\\\xb1\'', - 'const foo = \'foo\\\\xb1\'', - 'const foo = \'foo\\\\\\\\xb1\'', - 'const foo = \'\\\\xd8\\\\x3d\\\\xdc\\\\xa9\'', - 'const foo = \'foo\\\\x12foo\\\\x34\'', - 'const foo = \'\\\\\\\\xd8\\\\\\\\x3d\\\\\\\\xdc\\\\\\\\xa9\'', - 'const foo = \'foo\\\\\\\\x12foo\\\\\\\\x34\'', + String.raw`const foo = '\u00b1'`, + String.raw`const foo = '\u00b1\u00b1'`, + String.raw`const foo = 'foo\u00b1'`, + String.raw`const foo = 'foo\u00b1foo'`, + String.raw`const foo = '\u00b1foo'`, + String.raw`const foo = '\\xb1'`, + String.raw`const foo = '\\\\xb1'`, + String.raw`const foo = 'foo\\xb1'`, + String.raw`const foo = 'foo\\\\xb1'`, + String.raw`const foo = '\\xd8\\x3d\\xdc\\xa9'`, + String.raw`const foo = 'foo\\x12foo\\x34'`, + String.raw`const foo = '\\\\xd8\\\\x3d\\\\xdc\\\\xa9'`, + String.raw`const foo = 'foo\\\\x12foo\\\\x34'`, 'const foo = 42', 'const foo = `foo`', 'const foo = `\\u00b1`', @@ -38,82 +38,83 @@ const tests = { 'const foo = `foo\\\\x12foo\\\\x34`', 'const foo = `\\\\\\\\xd8\\\\\\\\x3d\\\\\\\\xdc\\\\\\\\xa9`', 'const foo = `foo\\\\\\\\x12foo\\\\\\\\x34`', + 'const foo = String.raw`\\\\xb1`', ], invalid: [ { - code: 'const foo = \'\\xb1\'', + code: String.raw`const foo = '\xb1'`, errors: [error], - output: 'const foo = \'\\u00b1\'', + output: String.raw`const foo = '\u00b1'`, }, { - code: 'const foo = \'\\\\\\xb1\'', + code: String.raw`const foo = '\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\u00b1\'', + output: String.raw`const foo = '\\\u00b1'`, }, { - code: 'const foo = \'\\xb1\\xb1\'', + code: String.raw`const foo = '\xb1\xb1'`, errors: [error], - output: 'const foo = \'\\u00b1\\u00b1\'', + output: String.raw`const foo = '\u00b1\u00b1'`, }, { - code: 'const foo = \'\\\\\\xb1\\\\\\xb1\'', + code: String.raw`const foo = '\\\xb1\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\u00b1\\\\\\u00b1\'', + output: String.raw`const foo = '\\\u00b1\\\u00b1'`, }, { - code: 'const foo = \'\\\\\\xb1\\\\\\\\xb1\'', + code: String.raw`const foo = '\\\xb1\\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\u00b1\\\\\\\\xb1\'', + output: String.raw`const foo = '\\\u00b1\\\\xb1'`, }, { - code: 'const foo = \'\\\\\\\\\\xb1\\\\\\xb1\'', + code: String.raw`const foo = '\\\\\xb1\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\\\\\u00b1\\\\\\u00b1\'', + output: String.raw`const foo = '\\\\\u00b1\\\u00b1'`, }, { - code: 'const foo = \'\\xb1foo\'', + code: String.raw`const foo = '\xb1foo'`, errors: [error], - output: 'const foo = \'\\u00b1foo\'', + output: String.raw`const foo = '\u00b1foo'`, }, { - code: 'const foo = \'\\xd8\\x3d\\xdc\\xa9\'', + code: String.raw`const foo = '\xd8\x3d\xdc\xa9'`, errors: [error], - output: 'const foo = \'\\u00d8\\u003d\\u00dc\\u00a9\'', + output: String.raw`const foo = '\u00d8\u003d\u00dc\u00a9'`, }, { - code: 'const foo = \'foo\\xb1\'', + code: String.raw`const foo = 'foo\xb1'`, errors: [error], - output: 'const foo = \'foo\\u00b1\'', + output: String.raw`const foo = 'foo\u00b1'`, }, { - code: 'const foo = \'foo\\\\\\xb1\'', + code: String.raw`const foo = 'foo\\\xb1'`, errors: [error], - output: 'const foo = \'foo\\\\\\u00b1\'', + output: String.raw`const foo = 'foo\\\u00b1'`, }, { - code: 'const foo = \'foo\\\\\\\\\\xb1\'', + code: String.raw`const foo = 'foo\\\\\xb1'`, errors: [error], - output: 'const foo = \'foo\\\\\\\\\\u00b1\'', + output: String.raw`const foo = 'foo\\\\\u00b1'`, }, { - code: 'const foo = \'foo\\x12foo\\x34\'', + code: String.raw`const foo = 'foo\x12foo\x34'`, errors: [error], - output: 'const foo = \'foo\\u0012foo\\u0034\'', + output: String.raw`const foo = 'foo\u0012foo\u0034'`, }, { - code: 'const foo = \'42\\x1242\\x34\'', + code: String.raw`const foo = '42\x1242\x34'`, errors: [error], - output: 'const foo = \'42\\u001242\\u0034\'', + output: String.raw`const foo = '42\u001242\u0034'`, }, { - code: 'const foo = \'42\\\\\\x1242\\\\\\x34\'', + code: String.raw`const foo = '42\\\x1242\\\x34'`, errors: [error], - output: 'const foo = \'42\\\\\\u001242\\\\\\u0034\'', + output: String.raw`const foo = '42\\\u001242\\\u0034'`, }, { - code: 'const foo = /^[\\x20-\\x7E]*$/', + code: String.raw`const foo = /^[\x20-\x7E]*$/`, errors: [error], - output: 'const foo = /^[\\u0020-\\u007E]*$/', + output: String.raw`const foo = /^[\u0020-\u007E]*$/`, }, // Test template literals { @@ -193,6 +194,17 @@ const tests = { // eslint-disable-next-line no-template-curly-in-string output: 'const foo = `\\u00b1${foo}\\u00b1${foo}`', }, + { + code: 'const foo = `\\xb1```', + errors: [error], + output: 'const foo = `\\u00b1```', + }, + // TODO: Not safe #2341 + { + code: 'const foo = tagged`\\xb1`', + errors: [error], + output: 'const foo = tagged`\\u00b1`', + }, ], }; @@ -203,6 +215,6 @@ test.typescript(avoidTestTitleConflict(tests, 'typescript')); test.snapshot({ valid: [], invalid: [ - 'const foo = "\\xb1"', + String.raw`const foo = "\xb1"`, ], }); diff --git a/test/no-instanceof-array.mjs b/test/no-instanceof-array.mjs index 2f7e394df3..aeba5420ff 100644 --- a/test/no-instanceof-array.mjs +++ b/test/no-instanceof-array.mjs @@ -64,7 +64,7 @@ test.snapshot({ '', '', '', - ].map(code => ({code, parser: parsers.vue})), + ].map(code => ({code, languageOptions: {parser: parsers.vue}})), ], }); diff --git a/test/no-invalid-fetch-options.mjs b/test/no-invalid-fetch-options.mjs new file mode 100644 index 0000000000..5ed1f977d9 --- /dev/null +++ b/test/no-invalid-fetch-options.mjs @@ -0,0 +1,97 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + 'fetch(url, {method: "POST", body})', + 'new Request(url, {method: "POST", body})', + 'fetch(url, {})', + 'new Request(url, {})', + 'fetch(url)', + 'new Request(url)', + 'fetch(url, {method: "UNKNOWN", body})', + 'new Request(url, {method: "UNKNOWN", body})', + 'fetch(url, {body: undefined})', + 'new Request(url, {body: undefined})', + 'fetch(url, {body: null})', + 'new Request(url, {body: null})', + 'fetch(url, {...options, body})', + 'new Request(url, {...options, body})', + 'new fetch(url, {body})', + 'Request(url, {body})', + 'not_fetch(url, {body})', + 'new not_Request(url, {body})', + 'fetch({body}, url)', + 'new Request({body}, url)', + 'fetch(url, {[body]: "foo=bar"})', + 'new Request(url, {[body]: "foo=bar"})', + outdent` + fetch(url, { + body: 'foo=bar', + body: undefined, + }); + `, + outdent` + new Request(url, { + body: 'foo=bar', + body: undefined, + }); + `, + outdent` + fetch(url, { + method: 'HEAD', + body: 'foo=bar', + method: 'post', + }); + `, + outdent` + new Request(url, { + method: 'HEAD', + body: 'foo=bar', + method: 'post', + }); + `, + ], + invalid: [ + 'fetch(url, {body})', + 'new Request(url, {body})', + 'fetch(url, {method: "GET", body})', + 'new Request(url, {method: "GET", body})', + 'fetch(url, {method: "HEAD", body})', + 'new Request(url, {method: "HEAD", body})', + 'fetch(url, {method: "head", body})', + 'new Request(url, {method: "head", body})', + 'const method = "head"; new Request(url, {method, body: "foo=bar"})', + 'const method = "head"; fetch(url, {method, body: "foo=bar"})', + 'fetch(url, {body}, extraArgument)', + 'new Request(url, {body}, extraArgument)', + outdent` + fetch(url, { + body: undefined, + body: 'foo=bar', + }); + `, + outdent` + new Request(url, { + body: undefined, + body: 'foo=bar', + }); + `, + outdent` + fetch(url, { + method: 'post', + body: 'foo=bar', + method: 'HEAD', + }); + `, + outdent` + new Request(url, { + method: 'post', + body: 'foo=bar', + method: 'HEAD', + }); + `, + ], +}); diff --git a/test/no-magic-array-flat-depth.mjs b/test/no-magic-array-flat-depth.mjs new file mode 100644 index 0000000000..721204cb8a --- /dev/null +++ b/test/no-magic-array-flat-depth.mjs @@ -0,0 +1,28 @@ +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + 'array.flat(1)', + 'array.flat(1.0)', + 'array.flat(0x01)', + 'array.flat(unknown)', + 'array.flat(Number.POSITIVE_INFINITY)', + 'array.flat(Infinity)', + 'array.flat(/* explanation */2)', + 'array.flat(2/* explanation */)', + 'array.flat()', + 'array.flat(2, extraArgument)', + 'new array.flat(2)', + 'array.flat?.(2)', + 'array.notFlat(2)', + 'flat(2)', + ], + invalid: [ + 'array.flat(2)', + 'array?.flat(2)', + 'array.flat(99,)', + 'array.flat(0b10,)', + ], +}); diff --git a/test/no-negation-in-equality-check.mjs b/test/no-negation-in-equality-check.mjs new file mode 100644 index 0000000000..70d0e58025 --- /dev/null +++ b/test/no-negation-in-equality-check.mjs @@ -0,0 +1,50 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + '!foo instanceof bar', + '+foo === bar', + '!(foo === bar)', + // We are not checking right side + 'foo === !bar', + ], + invalid: [ + '!foo === bar', + '!foo !== bar', + '!foo == bar', + '!foo != bar', + outdent` + function x() { + return!foo === bar; + } + `, + outdent` + function x() { + return! + foo === bar; + throw! + foo === bar; + } + `, + outdent` + foo + !(a) === b + `, + outdent` + foo + ![a, b].join('') === c + `, + outdent` + foo + ! [a, b].join('') === c + `, + outdent` + foo + !/* comment */[a, b].join('') === c + `, + '!!foo === bar', + ], +}); diff --git a/test/no-null.mjs b/test/no-null.mjs index 466df09529..40ce5d02c6 100644 --- a/test/no-null.mjs +++ b/test/no-null.mjs @@ -106,8 +106,10 @@ test.snapshot({ // #1146 test({ testerOptions: { - parserOptions: { - ecmaVersion: 2019, + languageOptions: { + parserOptions: { + ecmaVersion: 2019, + }, }, }, valid: [ diff --git a/test/no-static-only-class.mjs b/test/no-static-only-class.mjs index e8d244046e..a74d41c37d 100644 --- a/test/no-static-only-class.mjs +++ b/test/no-static-only-class.mjs @@ -211,10 +211,12 @@ test.typescript({ test.babel({ testerOptions: { - parserOptions: { - babelOptions: { - parserOpts: { - plugins: ['classStaticBlock'], + languageOptions: { + parserOptions: { + babelOptions: { + parserOpts: { + plugins: ['classStaticBlock'], + }, }, }, }, diff --git a/test/no-unnecessary-await.mjs b/test/no-unnecessary-await.mjs index 9ed31094b6..17cb005170 100644 --- a/test/no-unnecessary-await.mjs +++ b/test/no-unnecessary-await.mjs @@ -5,9 +5,11 @@ const {test} = getTester(import.meta); test.snapshot({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, diff --git a/test/no-useless-switch-case.mjs b/test/no-useless-switch-case.mjs index e3d87a4fe2..409d58355c 100644 --- a/test/no-useless-switch-case.mjs +++ b/test/no-useless-switch-case.mjs @@ -160,20 +160,3 @@ test.snapshot({ `, ], }); - -test.typescript({ - valid: [ - outdent` - switch (1) { - default: - handleDefaultCase1(); - break; - case 1: - default: - handleDefaultCase2(); - break; - } - `, - ], - invalid: [], -}); diff --git a/test/no-useless-undefined.mjs b/test/no-useless-undefined.mjs index 110099c05f..c95c7dde35 100644 --- a/test/no-useless-undefined.mjs +++ b/test/no-useless-undefined.mjs @@ -36,7 +36,6 @@ test({ 'assert.notPropertyVal(foo, "bar", undefined, message)', 'expect(foo).not(undefined)', 'expect(foo).to.have.property("bar", undefined)', - 'expect(foo).to.have.property("bar", undefined)', 'expect(foo).toBe(undefined)', 'expect(foo).toContain(undefined)', 'expect(foo).toContainEqual(undefined)', @@ -263,10 +262,12 @@ test({ code: 'return undefined;', output: 'return;', errors, - parserOptions: { - sourceType: 'script', - ecmaFeatures: { - globalReturn: true, + languageOptions: { + parserOptions: { + sourceType: 'script', + ecmaFeatures: { + globalReturn: true, + }, }, }, }, @@ -303,8 +304,6 @@ test.typescript({ 'const foo = (): string => undefined;', 'const foo = function (): undefined {return undefined}', 'export function foo(): undefined {return undefined}', - 'createContext(undefined);', - 'React.createContext(undefined);', outdent` const object = { method(): undefined { @@ -452,7 +451,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.vue, + languageOptions: {parser: parsers.vue}, }, valid: [ outdent` @@ -485,7 +484,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, valid: [], invalid: [ diff --git a/test/number-literal-case.mjs b/test/number-literal-case.mjs index 04268887ed..f633fc951b 100644 --- a/test/number-literal-case.mjs +++ b/test/number-literal-case.mjs @@ -12,9 +12,11 @@ const error = { // Legacy octal literals test({ testerOptions: { - parserOptions: { - ecmaVersion: 5, - sourceType: 'script', + languageOptions: { + parserOptions: { + ecmaVersion: 5, + sourceType: 'script', + }, }, }, valid: [ @@ -169,7 +171,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.vue, + languageOptions: {parser: parsers.vue}, }, valid: [ '', diff --git a/test/numeric-separators-style.mjs b/test/numeric-separators-style.mjs index d18060ba3e..4f3dc71be8 100644 --- a/test/numeric-separators-style.mjs +++ b/test/numeric-separators-style.mjs @@ -7,7 +7,7 @@ const error = { messageId: 'numeric-separators-style', }; -const legacyOctalParserOptions = {ecmaVersion: 6, sourceType: 'script'}; +const legacyOctalLanguageOptions = {ecmaVersion: 6, sourceType: 'script'}; // Most of these test cases copied from: // https://github.com/eslint/eslint/blob/master/tests/lib/rules/camelcase.js @@ -34,7 +34,7 @@ test({ 'const foo = 0777777', 'var foo = 0999999', 'let foo = 0111222', - ].map(code => ({code, parserOptions: legacyOctalParserOptions})), + ].map(code => ({code, languageOptions: legacyOctalLanguageOptions})), // Binary 'const foo = 0b1010_0001_1000_0101', diff --git a/test/package.mjs b/test/package.mjs index ebe78b6d32..33cd39cfe9 100644 --- a/test/package.mjs +++ b/test/package.mjs @@ -24,17 +24,6 @@ const deprecatedRules = Object.entries(eslintPluginUnicorn.rules) .filter(([, {meta: {deprecated}}]) => deprecated) .map(([ruleId]) => ruleId); -const testSorted = (t, actualOrder, sourceName) => { - actualOrder = actualOrder.filter(x => !ignoredRules.includes(x)); - const sortedOrder = [...actualOrder].sort(); - - for (const [wantedIndex, name] of sortedOrder.entries()) { - const actualIndex = actualOrder.indexOf(name); - const whereMessage = (wantedIndex === 0) ? '' : `, after '${sortedOrder[wantedIndex - 1]}'`; - t.is(actualIndex, wantedIndex, `${sourceName} should be alphabetically sorted, '${name}' should be placed at index ${wantedIndex}${whereMessage}`); - } -}; - const RULES_WITHOUT_PASS_FAIL_SECTIONS = new Set([ // Doesn't show code samples since it's just focused on filenames. 'filename-case', @@ -72,8 +61,6 @@ test('Every rule is defined in index file in alphabetical order', t => { ruleFiles.length - deprecatedRules.length, 'There are more rules than those exported in the all config.', ); - - testSorted(t, Object.keys(eslintPluginUnicorn.configs.recommended.rules), 'configs.recommended.rules'); }); test('validate configuration', async t => { @@ -184,11 +171,29 @@ function getCompactConfig(config) { test('flat configs', t => { t.deepEqual( - getCompactConfig(eslintPluginUnicorn.configs.recommended), + {...getCompactConfig(eslintPluginUnicorn.configs.recommended), name: 'unicorn/flat/recommended'}, {...eslintPluginUnicorn.configs['flat/recommended'], plugins: undefined}, ); t.deepEqual( - getCompactConfig(eslintPluginUnicorn.configs.all), + {...getCompactConfig(eslintPluginUnicorn.configs.all), name: 'unicorn/flat/all'}, {...eslintPluginUnicorn.configs['flat/all'], plugins: undefined}, ); }); + +test('rule.meta.docs.recommended should be synchronized with presets', t => { + for (const [name, rule] of Object.entries(eslintPluginUnicorn.rules)) { + if (deprecatedRules.includes(name)) { + continue; + } + + const {recommended} = rule.meta.docs; + t.is(typeof recommended, 'boolean', `meta.docs.recommended in '${name}' rule should be a boolean.`); + + const severity = eslintPluginUnicorn.configs.recommended.rules[`unicorn/${name}`]; + if (recommended) { + t.is(severity, 'error', `'${name}' rule should set to 'error'.`); + } else { + t.is(severity, 'off', `'${name}' rule should set to 'off'.`); + } + } +}); diff --git a/test/prefer-array-find.mjs b/test/prefer-array-find.mjs index 0ceee26232..a10a6fc7ee 100644 --- a/test/prefer-array-find.mjs +++ b/test/prefer-array-find.mjs @@ -567,7 +567,7 @@ test({ 'const foo = array.find(bar), first = foo[0];', 'const foo = array.filter(bar), first = notFoo[0];', 'const foo = array.filter(bar), first = foo[+0];', - 'const foo = array.filter(bar); first = foo;', + 'const foo2 = array.filter(bar); first = foo;', 'const foo = array.filter(bar), first = a[foo][0];', 'const foo = array.filter(bar), first = foo[-0];', 'const foo = array.filter(bar), first = foo[1-1];', @@ -923,15 +923,12 @@ test({ ], }); -// Check from last -const checkFromLastOptions = [{checkFromLast: true}]; - -// Default to false +// `checkFromLast` default to true test({ valid: [ 'array.filter(foo).pop()', 'array.filter(foo).at(-1)', - ], + ].map(code => ({code, options: [{checkFromLast: false}]})), invalid: [], }); @@ -968,7 +965,7 @@ test({ 'array.filter().pop()', 'array.filter(foo, thisArgument, extraArgument).pop()', 'array.filter(...foo).pop()', - ].map(code => ({code, options: checkFromLastOptions})), + ], invalid: [ { code: 'array.filter(foo).pop()', @@ -1005,7 +1002,7 @@ test({ `, errors: [{messageId: ERROR_POP}], }, - ].map(test => ({...test, options: checkFromLastOptions})), + ], }); // `.at(-1)` @@ -1058,7 +1055,7 @@ test({ 'array.filter().at(-1)', 'array.filter(foo, thisArgument, extraArgument).at(-1)', 'array.filter(...foo).at(-1)', - ].map(code => ({code, options: checkFromLastOptions})), + ], invalid: [ { code: 'array.filter(foo).at(-1)', @@ -1099,7 +1096,7 @@ test({ `, errors: [{messageId: ERROR_AT_MINUS_ONE}], }, - ].map(test => ({...test, options: checkFromLastOptions})), + ], }); // `.at(0)` diff --git a/test/prefer-array-flat.mjs b/test/prefer-array-flat.mjs index b60151c879..873e0753e7 100644 --- a/test/prefer-array-flat.mjs +++ b/test/prefer-array-flat.mjs @@ -229,7 +229,7 @@ test.snapshot({ // #1146 test({ testerOptions: { - parserOptions: { + languageOptions: { ecmaVersion: 2019, }, }, diff --git a/test/prefer-array-some.mjs b/test/prefer-array-some.mjs index 4c8f288758..181cbf5143 100644 --- a/test/prefer-array-some.mjs +++ b/test/prefer-array-some.mjs @@ -37,12 +37,12 @@ test({ 'find(fn)', // `callee.property` is not a `Identifier` 'foo["find"](fn)', - 'foo["fi" + "nd"](fn)', + 'foo["fi" + "nd"](fn) /* find */', 'foo[`find`](fn)', // Computed 'foo[find](fn)', // Not `.find` - 'foo.notFind(fn)', + 'foo.notFind(fn) /* find */', // More or less argument(s) 'foo.find()', 'foo.find(fn, thisArgument, extraArgument)', @@ -88,28 +88,28 @@ test({ }), // Actual messages { - code: 'if (foo.find(fn)) {}', + code: 'if (bar.find(fn)) {}', errors: [ { message: 'Prefer `.some(…)` over `.find(…)`.', suggestions: [ { desc: 'Replace `.find(…)` with `.some(…)`.', - output: 'if (foo.some(fn)) {}', + output: 'if (bar.some(fn)) {}', }, ], }, ], }, { - code: 'if (foo.findLast(fn)) {}', + code: 'if (bar.findLast(fn)) {}', errors: [ { message: 'Prefer `.some(…)` over `.findLast(…)`.', suggestions: [ { desc: 'Replace `.findLast(…)` with `.some(…)`.', - output: 'if (foo.some(fn)) {}', + output: 'if (bar.some(fn)) {}', }, ], }, @@ -210,6 +210,40 @@ test.snapshot({ ], }); +// `.{findIndex,findLastIndex}(…) !== -1` +// `.{findIndex,findLastIndex}(…) != -1` +// `.{findIndex,findLastIndex}(…) > -1` +// `.{findIndex,findLastIndex}(…) === -1` +// `.{findIndex,findLastIndex}(…) == -1` +// `.{findIndex,findLastIndex}(…) >= 0` +// `.{findIndex,findLastIndex}(…) < 0` +test.snapshot({ + valid: [ + 'foo.notMatchedMethod(bar) !== -1', + 'new foo.findIndex(bar) !== -1', + 'foo.findIndex(bar, extraArgument) !== -1', + 'foo.findIndex(bar) instanceof -1', + 'foo.findIndex(...bar) !== -1', + // We are not ignoring ``{_,lodash,underscore}.{findIndex,findLastIndex}` + // but it doesn't make sense to use them with one argument + '_.findIndex(bar)', + '_.findIndex(foo, bar)', + ], + invalid: [ + ...[ + 'foo.findIndex(bar) !== -1', + 'foo.findIndex(bar) != -1', + 'foo.findIndex(bar) > - 1', + 'foo.findIndex(bar) === -1', + 'foo.findIndex(bar) == - 1', + 'foo.findIndex(bar) >= 0', + 'foo.findIndex(bar) < 0', + ].flatMap(code => [code, code.replace('findIndex', 'findLastIndex')]), + 'foo.findIndex(bar) !== (( - 1 ))', + 'foo.findIndex(element => element.bar === 1) !== (( - 1 ))', + ], +}); + test.vue({ valid: [], invalid: [ diff --git a/test/prefer-dom-node-append.mjs b/test/prefer-dom-node-append.mjs index ad307f13d1..04033ced88 100644 --- a/test/prefer-dom-node-append.mjs +++ b/test/prefer-dom-node-append.mjs @@ -83,10 +83,6 @@ test({ code: 'node.appendChild(child) + 0;', errors: [error], }, - { - code: 'node.appendChild(child) + 0;', - errors: [error], - }, { code: '+node.appendChild(child);', errors: [error], diff --git a/test/prefer-dom-node-dataset.mjs b/test/prefer-dom-node-dataset.mjs index 722281d136..6b6dc2bc31 100644 --- a/test/prefer-dom-node-dataset.mjs +++ b/test/prefer-dom-node-dataset.mjs @@ -187,6 +187,8 @@ test.snapshot({ // First Argument is not startsWith `data-` 'element.getAttribute("foo-unicorn");', 'element.getAttribute("data");', + // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2307 + 'await page.locator("text=Hello").getAttribute("data-foo")', ], invalid: [ outdent` diff --git a/test/prefer-dom-node-remove.mjs b/test/prefer-dom-node-remove.mjs index d0b67b86d6..d41f690e66 100644 --- a/test/prefer-dom-node-remove.mjs +++ b/test/prefer-dom-node-remove.mjs @@ -4,64 +4,7 @@ import notDomNodeTypes from './utils/not-dom-node-types.mjs'; const {test} = getTester(import.meta); -const ERROR_MESSAGE_ID = 'error'; -const SUGGESTION_MESSAGE_ID = 'suggestion'; - -const invalidTestCase = ({code, output, suggestionOutput, suggestionOutputs}) => { - if (suggestionOutput) { - return { - code, - errors: [ - { - messageId: ERROR_MESSAGE_ID, - suggestions: [ - { - messageId: SUGGESTION_MESSAGE_ID, - data: {dotOrQuestionDot: '.'}, - output: suggestionOutput, - }, - ], - }, - ], - }; - } - - if (suggestionOutputs) { - return { - code, - errors: [ - { - messageId: ERROR_MESSAGE_ID, - suggestions: [ - { - messageId: SUGGESTION_MESSAGE_ID, - data: {dotOrQuestionDot: '?.'}, - output: suggestionOutputs[0], - }, - { - messageId: SUGGESTION_MESSAGE_ID, - data: {dotOrQuestionDot: '.'}, - output: suggestionOutputs[1], - }, - ], - }, - ], - }; - } - - return { - code, - output, - errors: [ - { - messageId: ERROR_MESSAGE_ID, - suggestions: undefined, - }, - ], - }; -}; - -test({ +test.snapshot({ valid: [ 'foo.remove()', 'this.remove()', @@ -93,217 +36,82 @@ test({ ], invalid: [ // Auto fix - { - code: 'parentNode.removeChild(foo)', - output: 'foo.remove()', - }, - { - code: 'parentNode.removeChild(this)', - output: 'this.remove()', - }, - { - code: 'parentNode.removeChild(some.node)', - output: 'some.node.remove()', - }, - { - code: 'parentNode.removeChild(getChild())', - output: 'getChild().remove()', - }, - { - code: 'parentNode.removeChild(lib.getChild())', - output: 'lib.getChild().remove()', - }, - { - code: 'parentNode.removeChild((() => childNode)())', - output: '(() => childNode)().remove()', - }, + 'parentNode.removeChild(foo)', + 'parentNode.removeChild(this)', + 'parentNode.removeChild(some.node)', + 'parentNode.removeChild(getChild())', + 'parentNode.removeChild(lib.getChild())', + 'parentNode.removeChild((() => childNode)())', // Need parenthesized - { - code: outdent` - async function foo () { - parentNode.removeChild( - await getChild() - ); - } - `, - output: outdent` - async function foo () { - (await getChild()).remove(); - } - `, - }, - { - code: outdent` - async function foo () { - parentNode.removeChild( - (await getChild()) - ); - } - `, - output: outdent` - async function foo () { - (await getChild()).remove(); - } - `, - }, - { - code: 'parentNode.removeChild((0, child))', - output: '(0, child).remove()', - }, - { - code: 'parentNode.removeChild( ( (new Image)) )', - output: '( (new Image)).remove()', - }, - { - code: 'parentNode.removeChild( new Audio )', - output: '(new Audio).remove()', - }, + outdent` + async function foo () { + parentNode.removeChild( + await getChild() + ); + } + `, + outdent` + async function foo () { + parentNode.removeChild( + (await getChild()) + ); + } + `, + 'parentNode.removeChild((0, child))', + 'parentNode.removeChild( ( (new Image)) )', + 'parentNode.removeChild( new Audio )', // Need semicolon - { - code: outdent` + outdent` + const array = [] + parentNode.removeChild([a, b, c].reduce(child => child, child)) + `, + outdent` + async function foo () { const array = [] - parentNode.removeChild([a, b, c].reduce(child => child, child)) - `, - output: outdent` + parentNode.removeChild( + await getChild() + ); + } + `, + outdent` + async function foo () { const array = [] - ;[a, b, c].reduce(child => child, child).remove() - `, - }, - { - code: outdent` - async function foo () { - const array = [] - parentNode.removeChild( - await getChild() - ); - } - `, - output: outdent` - async function foo () { - const array = [] - ;(await getChild()).remove(); - } - `, - }, - { - code: outdent` - async function foo () { - const array = [] - parentNode.removeChild( - (0, childNode) - ); - } - `, - output: outdent` - async function foo () { - const array = [] - ;(0, childNode).remove(); - } - `, - }, + parentNode.removeChild( + (0, childNode) + ); + } + `, // Value of `parentNode.removeChild` call is use - { - code: 'if (parentNode.removeChild(foo)) {}', - suggestionOutput: 'if (foo.remove()) {}', - }, - { - code: 'var removed = parentNode.removeChild(child);', - suggestionOutput: 'var removed = child.remove();', - }, - { - code: 'const foo = parentNode.removeChild(child);', - suggestionOutput: 'const foo = child.remove();', - }, - { - code: 'foo.bar(parentNode.removeChild(child));', - suggestionOutput: 'foo.bar(child.remove());', - }, - { - code: 'parentNode.removeChild(child) || "foo";', - suggestionOutput: 'child.remove() || "foo";', - }, - { - code: 'parentNode.removeChild(child) + 0;', - suggestionOutput: 'child.remove() + 0;', - }, - { - code: '+parentNode.removeChild(child);', - suggestionOutput: '+child.remove();', - }, - { - code: 'parentNode.removeChild(child) ? "foo" : "bar";', - suggestionOutput: 'child.remove() ? "foo" : "bar";', - }, - { - code: 'if (parentNode.removeChild(child)) {}', - suggestionOutput: 'if (child.remove()) {}', - }, - { - code: 'const foo = [parentNode.removeChild(child)]', - suggestionOutput: 'const foo = [child.remove()]', - }, - { - code: 'const foo = { bar: parentNode.removeChild(child) }', - suggestionOutput: 'const foo = { bar: child.remove() }', - }, - { - code: 'function foo() { return parentNode.removeChild(child); }', - suggestionOutput: 'function foo() { return child.remove(); }', - }, - { - code: 'const foo = () => { return parentElement.removeChild(child); }', - suggestionOutput: 'const foo = () => { return child.remove(); }', - }, - { - code: 'foo(bar = parentNode.removeChild(child))', - suggestionOutput: 'foo(bar = child.remove())', - }, + 'if (parentNode.removeChild(foo)) {}', + 'var removed = parentNode.removeChild(child);', + 'const foo = parentNode.removeChild(child);', + 'foo.bar(parentNode.removeChild(child));', + 'parentNode.removeChild(child) || "foo";', + 'parentNode.removeChild(child) + 0;', + '+parentNode.removeChild(child);', + 'parentNode.removeChild(child) ? "foo" : "bar";', + 'if (parentNode.removeChild(child)) {}', + 'const foo = [parentNode.removeChild(child)]', + 'const foo = { bar: parentNode.removeChild(child) }', + 'function foo() { return parentNode.removeChild(child); }', + 'const foo = () => { return parentElement.removeChild(child); }', + 'foo(bar = parentNode.removeChild(child))', // `parentNode` has side effect - { - code: 'foo().removeChild(child)', - suggestionOutput: 'child.remove()', - }, - { - code: 'foo[doSomething()].removeChild(child)', - suggestionOutput: 'child.remove()', - }, + 'foo().removeChild(child)', + 'foo[doSomething()].removeChild(child)', // Optional parent - { - code: 'parentNode?.removeChild(foo)', - suggestionOutputs: ['foo?.remove()', 'foo.remove()'], - }, - { - code: 'foo?.parentNode.removeChild(foo)', - output: 'foo?.remove()', - }, - { - code: 'foo.parentNode?.removeChild(foo)', - suggestionOutputs: ['foo?.remove()', 'foo.remove()'], - }, - { - code: 'foo?.parentNode?.removeChild(foo)', - suggestionOutputs: ['foo?.remove()', 'foo.remove()'], - }, - { - code: 'foo.bar?.parentNode.removeChild(foo.bar)', - suggestionOutputs: ['foo.bar?.remove()', 'foo.bar.remove()'], - }, - { - code: 'a.b?.c.parentNode.removeChild(foo)', - suggestionOutputs: ['foo?.remove()', 'foo.remove()'], - }, - { - code: 'a[b?.c].parentNode.removeChild(foo)', - output: 'foo.remove()', - }, + 'parentNode?.removeChild(foo)', + 'foo?.parentNode.removeChild(foo)', + 'foo.parentNode?.removeChild(foo)', + 'foo?.parentNode?.removeChild(foo)', + 'foo.bar?.parentNode.removeChild(foo.bar)', + 'a.b?.c.parentNode.removeChild(foo)', + 'a[b?.c].parentNode.removeChild(foo)', // The suggestions are bad, since they will break code - { - code: 'a?.b.parentNode.removeChild(a.b)', - suggestionOutputs: ['a.b?.remove()', 'a.b.remove()'], - }, - ].map(options => invalidTestCase(options)), + 'a?.b.parentNode.removeChild(a.b)', + ], }); diff --git a/test/prefer-export-from.mjs b/test/prefer-export-from.mjs index 5ee7584630..cfeb8939d0 100644 --- a/test/prefer-export-from.mjs +++ b/test/prefer-export-from.mjs @@ -320,7 +320,7 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.typescript, + languageOptions: {parser: parsers.typescript}, }, valid: [ // #1579 diff --git a/test/prefer-includes.mjs b/test/prefer-includes.mjs index 0ea72fd7fe..963d87149e 100644 --- a/test/prefer-includes.mjs +++ b/test/prefer-includes.mjs @@ -5,20 +5,22 @@ const {test} = getTester(import.meta); test.snapshot({ valid: [ - 'str.indexOf(\'foo\') !== -n', - 'str.indexOf(\'foo\') !== 1', - 'str.indexOf(\'foo\') === -2', - '!str.indexOf(\'foo\') === 1', - '!str.indexOf(\'foo\') === -n', + ...[ + 'str.indexOf(\'foo\') !== -n', + 'str.indexOf(\'foo\') !== 1', + 'str.indexOf(\'foo\') === -2', + '!str.indexOf(\'foo\') === 1', + '!str.indexOf(\'foo\') === -n', + 'null.indexOf(\'foo\') !== 1', + 'something.indexOf(foo, 0, another) !== -1', + '_.indexOf(foo, bar) !== -1', + 'lodash.indexOf(foo, bar) !== -1', + 'underscore.indexOf(foo, bar) !== -1', + ].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]), 'str.includes(\'foo\')', '\'foobar\'.includes(\'foo\')', '[1,2,3].includes(4)', - 'null.indexOf(\'foo\') !== 1', 'f(0) < 0', - 'something.indexOf(foo, 0, another) !== -1', - '_.indexOf(foo, bar) !== -1', - 'lodash.indexOf(foo, bar) !== -1', - 'underscore.indexOf(foo, bar) !== -1', ], invalid: [ '\'foobar\'.indexOf(\'foo\') !== -1', @@ -32,7 +34,7 @@ test.snapshot({ '(a || b).indexOf(\'foo\') === -1', 'foo.indexOf(bar, 0) !== -1', 'foo.indexOf(bar, 1) !== -1', - ], + ].flatMap(code => [code, code.replace('.indexOf', '.lastIndexOf')]), }); const {snapshot, typescript} = tests({ diff --git a/test/prefer-keyboard-event-key.mjs b/test/prefer-keyboard-event-key.mjs index 440cac3947..f98c63c405 100644 --- a/test/prefer-keyboard-event-key.mjs +++ b/test/prefer-keyboard-event-key.mjs @@ -525,24 +525,6 @@ test({ `, errors: [error('charCode'), error('keyCode')], }, - { - code: outdent` - const e = {} - foo.addEventListener('click', (e, r, fg) => { - function a() { - if (true) { - { - { - const { charCode } = e; - console.log(e.keyCode, charCode); - } - } - } - } - }); - `, - errors: [error('charCode'), error('keyCode')], - }, { code: outdent` foo123.addEventListener('click', event => { diff --git a/test/prefer-module.mjs b/test/prefer-module.mjs index 81b7836fff..2e1497c0d7 100644 --- a/test/prefer-module.mjs +++ b/test/prefer-module.mjs @@ -57,10 +57,12 @@ test({ } `, errors: 1, - parserOptions: { + languageOptions: { sourceType: 'script', - ecmaFeatures: { - globalReturn: true, + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, }, }, }, diff --git a/test/prefer-number-properties.mjs b/test/prefer-number-properties.mjs index f6970fa3a2..9c218c3e43 100644 --- a/test/prefer-number-properties.mjs +++ b/test/prefer-number-properties.mjs @@ -182,6 +182,33 @@ const errorNaN = [ }, ]; +const errorPositiveInfinity = [ + { + messageId: MESSAGE_ID_ERROR, + data: { + description: 'Infinity', + property: 'POSITIVE_INFINITY', + }, + }, +]; + +const errorNegativeInfinity = [ + { + messageId: MESSAGE_ID_ERROR, + data: { + description: '-Infinity', + property: 'NEGATIVE_INFINITY', + }, + }, +]; + +function withCheckInfinity(code) { + return { + code, + options: [{checkInfinity: true}], + }; +} + test({ valid: [ 'const foo = Number.NaN;', @@ -252,13 +279,11 @@ test({ 'function Infinity() {}', 'class Infinity {}', 'class Foo { Infinity(){}}', + 'const foo = Infinity;', + 'const foo = -Infinity;', { - code: 'const foo = Infinity;', - options: [{checkInfinity: false}], - }, - { - code: 'const foo = -Infinity;', - options: [{checkInfinity: false}], + code: 'const foo = NaN', + options: [{checkNaN: false}], }, ], invalid: [ @@ -307,6 +332,16 @@ test({ output: 'class Foo3 {[Number.NaN] = 1}', errors: errorNaN, }, + { + ...withCheckInfinity('const foo = Infinity;'), + output: 'const foo = Number.POSITIVE_INFINITY;', + errors: errorPositiveInfinity, + }, + { + ...withCheckInfinity('const foo = -Infinity;'), + output: 'const foo = Number.NEGATIVE_INFINITY;', + errors: errorNegativeInfinity, + }, ], }); @@ -370,30 +405,28 @@ test.snapshot({ 'foo[NaN] = 1;', 'class A {[NaN](){}}', 'foo = {[NaN]: 1}', - - 'const foo = Infinity;', - 'if (Number.isNaN(Infinity)) {}', - 'if (Object.is(foo, Infinity)) {}', - 'const foo = bar[Infinity];', - 'const foo = {Infinity};', - 'const foo = {Infinity: Infinity};', - 'const foo = {[Infinity]: -Infinity};', - 'const foo = {[-Infinity]: Infinity};', - 'const foo = {Infinity: -Infinity};', - 'const {foo = Infinity} = {};', - 'const {foo = -Infinity} = {};', - 'const foo = Infinity.toString();', - 'const foo = -Infinity.toString();', - 'const foo = (-Infinity).toString();', - 'const foo = +Infinity;', - 'const foo = +-Infinity;', - 'const foo = -Infinity;', - 'const foo = -(-Infinity);', - 'const foo = 1 - Infinity;', - 'const foo = 1 - -Infinity;', - 'const isPositiveZero = value => value === 0 && 1 / value === Infinity;', - 'const isNegativeZero = value => value === 0 && 1 / value === -Infinity;', - + withCheckInfinity('const foo = Infinity;'), + withCheckInfinity('if (Number.isNaN(Infinity)) {}'), + withCheckInfinity('if (Object.is(foo, Infinity)) {}'), + withCheckInfinity('const foo = bar[Infinity];'), + withCheckInfinity('const foo = {Infinity};'), + withCheckInfinity('const foo = {Infinity: Infinity};'), + withCheckInfinity('const foo = {[Infinity]: -Infinity};'), + withCheckInfinity('const foo = {[-Infinity]: Infinity};'), + withCheckInfinity('const foo = {Infinity: -Infinity};'), + withCheckInfinity('const {foo = Infinity} = {};'), + withCheckInfinity('const {foo = -Infinity} = {};'), + withCheckInfinity('const foo = Infinity.toString();'), + withCheckInfinity('const foo = -Infinity.toString();'), + withCheckInfinity('const foo = (-Infinity).toString();'), + withCheckInfinity('const foo = +Infinity;'), + withCheckInfinity('const foo = +-Infinity;'), + withCheckInfinity('const foo = -Infinity;'), + withCheckInfinity('const foo = -(-Infinity);'), + withCheckInfinity('const foo = 1 - Infinity;'), + withCheckInfinity('const foo = 1 - -Infinity;'), + withCheckInfinity('const isPositiveZero = value => value === 0 && 1 / value === Infinity;'), + withCheckInfinity('const isNegativeZero = value => value === 0 && 1 / value === -Infinity;'), 'const {a = NaN} = {};', 'const {[NaN]: a = NaN} = {};', 'const [a = NaN] = [];', @@ -402,7 +435,7 @@ test.snapshot({ 'function foo([a = NaN]) {}', // Space after keywords - 'function foo() {return-Infinity}', + withCheckInfinity('function foo() {return-Infinity}'), 'globalThis.isNaN(foo);', 'global.isNaN(foo);', @@ -413,7 +446,7 @@ test.snapshot({ 'window.parseFloat(foo);', 'self.parseFloat(foo);', 'globalThis.NaN', - '-globalThis.Infinity', + withCheckInfinity('-globalThis.Infinity'), // Not a call outdent` diff --git a/test/prefer-regexp-test.mjs b/test/prefer-regexp-test.mjs index 459e38c781..fbda161dbd 100644 --- a/test/prefer-regexp-test.mjs +++ b/test/prefer-regexp-test.mjs @@ -207,7 +207,15 @@ test({ : { code, errors: [ - {suggestions: [{output}]}, + { + message: 'Prefer `.test(…)` over `.exec(…)`.', + suggestions: [ + { + desc: 'Switch to `RegExp#test(…)`.', + output, + }, + ], + }, ], }, ), diff --git a/test/prefer-set-has.mjs b/test/prefer-set-has.mjs index 54733f26dc..05f7b829fb 100644 --- a/test/prefer-set-has.mjs +++ b/test/prefer-set-has.mjs @@ -624,13 +624,15 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.babel, - parserOptions: { - babelOptions: { - parserOpts: { - plugins: [ - ['decorators', {decoratorsBeforeExport: true}], - ], + languageOptions: { + parser: parsers.babel, + parserOptions: { + babelOptions: { + parserOpts: { + plugins: [ + ['decorators', {decoratorsBeforeExport: true}], + ], + }, }, }, }, @@ -659,7 +661,9 @@ test.snapshot({ test.snapshot({ testerOptions: { - parser: parsers.typescript, + languageOptions: { + parser: parsers.typescript, + }, }, valid: [ // https://github.com/TheThingsNetwork/lorawan-stack/blob/1dab30227e632ceade425e0c67d5f84316e830da/pkg/webui/console/containers/device-importer/index.js#L74 diff --git a/test/prefer-spread.mjs b/test/prefer-spread.mjs index 83edafc383..42783a8867 100644 --- a/test/prefer-spread.mjs +++ b/test/prefer-spread.mjs @@ -274,7 +274,7 @@ test.snapshot({ 'do foo.concat(1); while (test)', { code: 'with (foo) foo.concat(1)', - parserOptions: {ecmaVersion: 6, sourceType: 'script'}, + languageOptions: {parserOptions: {ecmaVersion: 6, sourceType: 'script'}}, }, // Code from example in docs outdent` diff --git a/test/prefer-string-raw.mjs b/test/prefer-string-raw.mjs new file mode 100644 index 0000000000..af5e034c7a --- /dev/null +++ b/test/prefer-string-raw.mjs @@ -0,0 +1,56 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + String.raw`a = '\''`, + // Cannot use `String.raw` + String.raw`'a\\b'`, + String.raw`import foo from "./foo\\bar.js";`, + String.raw`export {foo} from "./foo\\bar.js";`, + String.raw`export * from "./foo\\bar.js";`, + String.raw`a = {'a\\b': ''}`, + outdent` + a = "\\\\a \\ + b" + `, + String.raw`a = 'a\\b\u{51}c'`, + 'a = "a\\\\b`"', + // eslint-disable-next-line no-template-curly-in-string + 'a = "a\\\\b${foo}"', + { + code: String.raw``, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + ], + invalid: [ + String.raw`a = 'a\\b'`, + String.raw`a = {['a\\b']: b}`, + String.raw`function a() {return'a\\b'}`, + String.raw`const foo = "foo \\x46";`, + ], +}); + +test.typescript({ + valid: [ + outdent` + enum Files { + Foo = "C:\\\\path\\\\to\\\\foo.js", + } + `, + outdent` + enum Foo { + "\\\\a\\\\b" = "baz", + } + `, + ], + invalid: [], +}); diff --git a/test/prefer-string-replace-all.mjs b/test/prefer-string-replace-all.mjs index 6fb1158feb..bc742d4fe1 100644 --- a/test/prefer-string-replace-all.mjs +++ b/test/prefer-string-replace-all.mjs @@ -14,8 +14,8 @@ test.snapshot({ // Not 2 arguments 'foo.replace(/a/g)', 'foo.replaceAll(/a/g)', - 'foo.replace(/\\\\./g)', - 'foo.replaceAll(/\\\\./g)', + String.raw`foo.replace(/\\./g)`, + String.raw`foo.replaceAll(/\\./g)`, // Not `CallExpression` 'new foo.replace(/a/g, bar)', 'new foo.replaceAll(/a/g, bar)', @@ -61,11 +61,11 @@ test.snapshot({ ) `, // Quotes - 'foo.replace(/"\'/g, \'\\\'\')', + String.raw`foo.replace(/"'/g, '\'')`, // Escaped symbols - 'foo.replace(/\\./g, bar)', - 'foo.replace(/\\\\\\./g, bar)', - 'foo.replace(/\\|/g, bar)', + String.raw`foo.replace(/\./g, bar)`, + String.raw`foo.replace(/\\\./g, bar)`, + String.raw`foo.replace(/\|/g, bar)`, // `u` flag 'foo.replace(/a/gu, bar)', 'foo.replace(/a/ug, bar)', @@ -74,10 +74,10 @@ test.snapshot({ 'foo.replace(/a?/g, bar)', 'foo.replace(/.*/g, bar)', 'foo.replace(/a|b/g, bar)', - 'foo.replace(/\\W/g, bar)', - 'foo.replace(/\\u{61}/g, bar)', - 'foo.replace(/\\u{61}/gu, bar)', - 'foo.replace(/\\u{61}/gv, bar)', + String.raw`foo.replace(/\W/g, bar)`, + String.raw`foo.replace(/\u{61}/g, bar)`, + String.raw`foo.replace(/\u{61}/gu, bar)`, + String.raw`foo.replace(/\u{61}/gv, bar)`, 'foo.replace(/]/g, "bar")', // Extra flag 'foo.replace(/a/gi, bar)', @@ -92,20 +92,20 @@ test.snapshot({ 'foo.replace(/[a]/g, _)', 'foo.replace(/a{1/g, _)', 'foo.replace(/a{1}/g, _)', - 'foo.replace(/\\u0022/g, _)', - 'foo.replace(/\\u0027/g, _)', - 'foo.replace(/\\cM\\cj/g, _)', - 'foo.replace(/\\x22/g, _)', - 'foo.replace(/\\x27/g, _)', - 'foo.replace(/\\uD83D\\ude00/g, _)', - 'foo.replace(/\\u{1f600}/gu, _)', - 'foo.replace(/\\n/g, _)', - 'foo.replace(/\\u{20}/gu, _)', - 'foo.replace(/\\u{20}/gv, _)', + String.raw`foo.replace(/\u0022/g, _)`, + String.raw`foo.replace(/\u0027/g, _)`, + String.raw`foo.replace(/\cM\cj/g, _)`, + String.raw`foo.replace(/\x22/g, _)`, + String.raw`foo.replace(/\x27/g, _)`, + String.raw`foo.replace(/\uD83D\ude00/g, _)`, + String.raw`foo.replace(/\u{1f600}/gu, _)`, + String.raw`foo.replace(/\n/g, _)`, + String.raw`foo.replace(/\u{20}/gu, _)`, + String.raw`foo.replace(/\u{20}/gv, _)`, 'foo.replaceAll(/a]/g, _)', - 'foo.replaceAll(/\\r\\n\\u{1f600}/gu, _)', - 'foo.replaceAll(/\\r\\n\\u{1f600}/gv, _)', + String.raw`foo.replaceAll(/\r\n\u{1f600}/gu, _)`, + String.raw`foo.replaceAll(/\r\n\u{1f600}/gv, _)`, `foo.replaceAll(/a${' very'.repeat(30)} long string/g, _)`, // Invalid RegExp #2010 diff --git a/test/prefer-string-slice.mjs b/test/prefer-string-slice.mjs index 4fd035da3b..7776d86c55 100644 --- a/test/prefer-string-slice.mjs +++ b/test/prefer-string-slice.mjs @@ -82,25 +82,11 @@ test({ output: '"foo".slice()', errors: errorsSubstr, }, - { - code: '"foo".substr(1)', - output: '"foo".slice(1)', - errors: errorsSubstr, - }, - { - code: '"foo".substr(1, 2)', - output: '"foo".slice(1, 3)', - errors: errorsSubstr, - }, { code: '"foo".substr(bar.length, Math.min(baz, 100))', output: '"foo".slice(bar.length, bar.length + Math.min(baz, 100))', errors: errorsSubstr, }, - { - code: '"foo".substr(1, length)', - errors: errorsSubstr, - }, { code: '"foo".substr(1, "abc".length)', output: '"foo".slice(1, 1 + "abc".length)', @@ -268,11 +254,6 @@ test({ output: 'foo.slice(Math.max(0, start))', errors: errorsSubstring, }, - { - code: '"foo".substring(1)', - output: '"foo".slice(1)', - errors: errorsSubstring, - }, { code: 'foo.substring(start, end)', errors: errorsSubstring, diff --git a/test/prefer-string-starts-ends-with.mjs b/test/prefer-string-starts-ends-with.mjs index 4af56909e5..8afcb78f5e 100644 --- a/test/prefer-string-starts-ends-with.mjs +++ b/test/prefer-string-starts-ends-with.mjs @@ -95,11 +95,11 @@ test({ // String in variable. Don't autofix known, non-strings which don't have a startsWith/endsWith function. { code: 'const foo = {}; /^abc/.test(foo);', - errors: [{messageId: MESSAGE_STARTS_WITH}], + errors: [{messageId: MESSAGE_STARTS_WITH, suggestions: 3}], }, { code: 'const foo = 123; /^abc/.test(foo);', - errors: [{messageId: MESSAGE_STARTS_WITH}], + errors: [{messageId: MESSAGE_STARTS_WITH, suggestions: 3}], }, { code: 'const foo = "hello"; /^abc/.test(foo);', diff --git a/test/prefer-structured-clone.mjs b/test/prefer-structured-clone.mjs new file mode 100644 index 0000000000..3543e6abe5 --- /dev/null +++ b/test/prefer-structured-clone.mjs @@ -0,0 +1,75 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +// `JSON.parse(JSON.stringify(…))` +test.snapshot({ + valid: [ + 'structuredClone(foo)', + 'JSON.parse(new JSON.stringify(foo))', + 'new JSON.parse(JSON.stringify(foo))', + 'JSON.parse(JSON.stringify())', + 'JSON.parse(JSON.stringify(...foo))', + 'JSON.parse(JSON.stringify(foo, extraArgument))', + 'JSON.parse(...JSON.stringify(foo))', + 'JSON.parse(JSON.stringify(foo), extraArgument)', + 'JSON.parse(JSON.stringify?.(foo))', + 'JSON.parse(JSON?.stringify(foo))', + 'JSON.parse?.(JSON.stringify(foo))', + 'JSON?.parse(JSON.stringify(foo))', + 'JSON.parse(JSON.not_stringify(foo))', + 'JSON.parse(not_JSON.stringify(foo))', + 'JSON.not_parse(JSON.stringify(foo))', + 'not_JSON.parse(JSON.stringify(foo))', + 'JSON.stringify(JSON.parse(foo))', + // Not checking + 'JSON.parse(JSON.stringify(foo, undefined, 2))', + ], + invalid: [ + 'JSON.parse(JSON.stringify(foo))', + 'JSON.parse(JSON.stringify(foo),)', + 'JSON.parse(JSON.stringify(foo,))', + 'JSON.parse(JSON.stringify(foo,),)', + 'JSON.parse( ((JSON.stringify)) (foo))', + '(( JSON.parse)) (JSON.stringify(foo))', + 'JSON.parse(JSON.stringify( ((foo)) ))', + outdent` + function foo() { + return JSON + .parse( + JSON. + stringify( + bar, + ), + ); + } + `, + ], +}); + +// Custom functions +test.snapshot({ + valid: [ + 'new _.cloneDeep(foo)', + 'notMatchedFunction(foo)', + '_.cloneDeep()', + '_.cloneDeep(...foo)', + '_.cloneDeep(foo, extraArgument)', + '_.cloneDeep?.(foo)', + '_?.cloneDeep(foo)', + ], + invalid: [ + '_.cloneDeep(foo)', + 'lodash.cloneDeep(foo)', + 'lodash.cloneDeep(foo,)', + { + code: 'myCustomDeepCloneFunction(foo,)', + options: [{functions: ['myCustomDeepCloneFunction']}], + }, + { + code: 'my.cloneDeep(foo,)', + options: [{functions: ['my.cloneDeep']}], + }, + ], +}); diff --git a/test/prefer-ternary.mjs b/test/prefer-ternary.mjs index cc71982549..f8247d77a7 100644 --- a/test/prefer-ternary.mjs +++ b/test/prefer-ternary.mjs @@ -1214,20 +1214,6 @@ test({ `, errors, }, - { - code: outdent` - function unicorn() { - if (test) return a; - else return b; - } - `, - output: outdent` - function unicorn() { - return test ? a : b; - } - `, - errors, - }, // Precedence { diff --git a/test/prefer-top-level-await.mjs b/test/prefer-top-level-await.mjs index f7a5ecc62d..4c8e973598 100644 --- a/test/prefer-top-level-await.mjs +++ b/test/prefer-top-level-await.mjs @@ -143,7 +143,7 @@ test.snapshot({ async function foo() {} foo(); `, - parserOptions: {sourceType: 'script'}, + languageOptions: {parserOptions: {sourceType: 'script'}}, }, { code: outdent` @@ -151,7 +151,7 @@ test.snapshot({ async function foo() {} async function foo() {} `, - parserOptions: {sourceType: 'script'}, + languageOptions: {parserOptions: {sourceType: 'script'}}, }, outdent` const program = {async run () {}}; diff --git a/test/prevent-abbreviations.mjs b/test/prevent-abbreviations.mjs index 015f8abd62..17d411bd24 100644 --- a/test/prevent-abbreviations.mjs +++ b/test/prevent-abbreviations.mjs @@ -3,15 +3,7 @@ import {getTester, avoidTestTitleConflict} from './utils/test.mjs'; const {test} = getTester(import.meta); -const createErrors = message => { - const error = {}; - - if (message) { - error.message = message; - } - - return [error]; -}; +const createErrors = message => [{message}]; const extendedOptions = [ { @@ -105,6 +97,15 @@ const noExtendDefaultAllowListOptions = [ ]; const tests = { + testerOptions: { + languageOptions: { + globals: { + document: 'readonly', + event: 'readonly', + Response: 'readonly', + }, + }, + }, valid: [ 'let x', '({x: 1})', @@ -172,7 +173,6 @@ const tests = { `, 'foo.err = 1', 'foo.bar.err = 1', - 'this.err = 1', outdent` class C { err() {} @@ -341,30 +341,30 @@ const tests = { { code: 'let successCb', output: 'let successCallback', - errors: createErrors(), + errors: 1, }, { code: 'let btnColor', output: 'let buttonColor', - errors: createErrors(), + errors: 1, }, { code: 'this.successCb = 1', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'this.btnColor = 1', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, // This tests that the rule does not hang up on combinatoric explosion of possible replacements { code: 'let ' + 'CbE'.repeat(1024), output: 'let ' + 'CallbackE'.repeat(1024), - errors: createErrors(), + errors: 1, }, { @@ -387,113 +387,86 @@ const tests = { { code: 'let args', output: 'let arguments_', - errors: createErrors(), + errors: 1, }, { code: 'let args', output: 'let arguments_', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'let args', output: 'let arguments_', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'let c', output: 'let custom', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'function cb() {}', output: 'function callback() {}', - errors: createErrors(), + errors: 1, }, { code: 'class cb {}', output: 'class circuitBreacker {}', options: extendedOptions, - errors: createErrors(), - }, - - { - code: 'let e', - errors: createErrors(), + errors: 1, }, { code: 'let e', options: customOptions, - errors: createErrors(), + errors: 1, }, - { - code: 'let err', - output: 'let error', - errors: createErrors(), - }, { code: 'let err', output: 'let error', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'let err', output: 'let error', options: customOptions, - errors: createErrors(), - }, - - { - code: '({err: 1})', - options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'let errCb', output: 'let errorCallback', - errors: createErrors(), + errors: 1, }, { code: 'let errCb', output: 'let errorCircuitBreacker', options: extendedOptions, - errors: createErrors(), + errors: 1, }, { code: 'let errCb', output: 'let handleError', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'let ErrCb', output: 'let HandleError', options: customOptions, - errors: createErrors(), - }, - { - code: 'let ErrCb', - output: 'let ErrorCallback', - errors: createErrors(), + errors: 1, }, { code: 'let ErrCb', output: 'let ErrorCircuitBreacker', options: extendedOptions, - errors: createErrors(), - }, - { - code: 'let ErrCb', - output: 'let HandleError', - options: customOptions, - errors: createErrors(), + errors: 1, }, // `errCb` should not match this @@ -501,19 +474,13 @@ const tests = { code: 'let fooErrCb', output: 'let fooErrorCb', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'let errCbFoo', output: 'let errorCbFoo', options: customOptions, - errors: createErrors(), - }, - - { - code: 'class Err {}', - output: 'class Error_ {}', - errors: createErrors(), + errors: 1, }, { @@ -521,7 +488,7 @@ const tests = { let e; console.log(e); `, - errors: createErrors(), + errors: 1, }, { @@ -533,7 +500,7 @@ const tests = { let error; console.log(error); `, - errors: createErrors(), + errors: 1, }, { @@ -549,7 +516,7 @@ const tests = { let error_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -566,7 +533,7 @@ const tests = { let error__; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -583,7 +550,7 @@ const tests = { console.log(error); } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -600,13 +567,13 @@ const tests = { console.log(error, error_); } `, - errors: createErrors(), + errors: 1, }, { code: 'err => err', output: 'error => error', - errors: createErrors(), + errors: 1, }, { @@ -618,7 +585,7 @@ const tests = { const options = {}; console.log(options); `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -631,7 +598,7 @@ const tests = { var options = 2; console.log(options); `, - errors: createErrors(), + errors: 1, }, { @@ -643,7 +610,7 @@ const tests = { const error = {}; const foo = {err: error}; `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -662,32 +629,32 @@ const tests = { b: 2 }; `, - errors: createErrors(), + errors: 1, }, { code: '({err}) => err', output: '({err: error}) => error', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'err => ({err})', output: 'error => ({err: error})', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'const {err} = foo;', output: 'const {err: error} = foo;', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'const foo = {err: 1}', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -696,22 +663,17 @@ const tests = { }; `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'foo.err = 1', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: 'foo.bar.err = 1', options: checkPropertiesOptions, - errors: createErrors(), - }, - { - code: 'this.err = 1', - options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { @@ -721,7 +683,7 @@ const tests = { } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { @@ -743,12 +705,12 @@ const tests = { { code: 'let err_', output: 'let error_', - errors: createErrors(), + errors: 1, }, { code: 'let __err__', output: 'let __error__', - errors: createErrors(), + errors: 1, }, { code: 'let _e', @@ -772,12 +734,12 @@ const tests = { { code: 'const Err = 1;', output: 'const Error_ = 1;', - errors: createErrors(), + errors: 1, }, { code: 'const _Err_ = 1;', output: 'const _Error_ = 1;', - errors: createErrors(), + errors: 1, }, { code: '({Err: 1})', @@ -793,7 +755,7 @@ const tests = { { code: 'let doc', output: 'let document_', - errors: createErrors(), + errors: 1, }, // This test need run eslint 3 times to get the correct result @@ -880,7 +842,7 @@ const tests = { { code: 'let pkg', output: 'let package_', - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -931,7 +893,7 @@ const tests = { code: 'let y', output: 'let yield_', options: customOptions, - errors: createErrors(), + errors: 1, }, { @@ -963,7 +925,7 @@ const tests = { console.log(errorCallback, errorCallback_); } `, - errors: createErrors().concat(createErrors()) + errors: 1.concat(1) }, */ { @@ -993,7 +955,7 @@ const tests = { let error; ({a: error = 1} = 2); `, - errors: createErrors(), + errors: 1, }, // Renaming to `arguments` would result in a `SyntaxError`, so it should rename to `arguments_` @@ -1006,7 +968,7 @@ const tests = { 'use strict'; let arguments_; `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1023,7 +985,7 @@ const tests = { } } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1040,7 +1002,7 @@ const tests = { } } `, - errors: createErrors(), + errors: 1, }, { @@ -1054,7 +1016,7 @@ const tests = { return arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1069,7 +1031,7 @@ const tests = { return arguments_; } `, - errors: createErrors(), + errors: 1, }, // Renaming to `arguments` whould result in `f` returning it's arguments instead of the outer variable @@ -1086,7 +1048,7 @@ const tests = { return arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1101,7 +1063,7 @@ const tests = { return arguments + arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1116,7 +1078,7 @@ const tests = { return g.apply(this, arguments) + arguments_; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1131,7 +1093,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { @@ -1181,7 +1143,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1200,7 +1162,7 @@ const tests = { }; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1217,7 +1179,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1234,7 +1196,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1251,7 +1213,7 @@ const tests = { return property; } `, - errors: createErrors(), + errors: 1, }, // `extendDefaultAllowList` option @@ -1259,40 +1221,40 @@ const tests = { code: 'const propTypes = 2;const err = 2;', output: 'const propertyTypes = 2;const err = 2;', options: noExtendDefaultAllowListOptions, - errors: createErrors(), + errors: 1, }, // #1937 { code: 'const expectedRetVal = "that should be ok";', output: 'const expectedReturnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const retVal = "that should be ok";', output: 'const returnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const retValue = "that should be ok";', output: 'const returnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const returnVal = "that should be ok";', output: 'const returnValue = "that should be ok";', - errors: createErrors(), + errors: 1, }, { code: 'const sendDmMessage = () => {};', output: 'const sendDirectMessage = () => {};', options: [{replacements: {dm: {directMessage: true}}}], - errors: createErrors(), + errors: 1, }, { code: 'const ret_val = "that should be ok";', output: 'const returnValue_value = "that should be ok";', - errors: createErrors(), + errors: 1, }, ], }; @@ -1303,12 +1265,13 @@ test.typescript(avoidTestTitleConflict(tests, 'typescript')); test({ testerOptions: { - parserOptions: { + languageOptions: { sourceType: 'script', ecmaVersion: 5, - }, - env: { - browser: true, + globals: { + document: 'readonly', + event: 'readonly', + }, }, }, valid: [], @@ -1316,7 +1279,7 @@ test({ { code: 'var doc', output: 'var document_', - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1327,14 +1290,14 @@ test({ var document_; document.querySelector(document_); `, - errors: createErrors(), + errors: 1, }, { code: 'var y', output: 'var yield_', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1346,23 +1309,23 @@ test({ var yield_; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'function a() {try {} catch(args) {}}', output: 'function a() {try {} catch(arguments_) {}}', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'var one', options: [{replacements: {one: {1: true}}}], - errors: createErrors(), + errors: 1, }, { code: 'var one_two', options: [{replacements: {one: {first: true, 1: true}}}], - errors: createErrors(), + errors: 1, }, ], }); @@ -1478,7 +1441,7 @@ const importExportTests = { import error from 'err'; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1488,13 +1451,13 @@ const importExportTests = { import {err as error} from 'err'; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'import {err as err} from "err";', output: 'import {err as error} from "err";', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1512,7 +1475,7 @@ const importExportTests = { } from 'foo'; `, options: customOptions, - errors: createErrors(), + errors: 1, }, { @@ -1522,7 +1485,7 @@ const importExportTests = { output: outdent` import {err as callback} from 'err'; `, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1531,54 +1494,54 @@ const importExportTests = { output: outdent` const {err: callback} = foo; `, - errors: createErrors(), + errors: 1, }, // Internal import { code: 'const err = require("../err")', output: 'const error = require("../err")', - errors: createErrors(), + errors: 1, }, { code: 'const err = require("/err")', output: 'const error = require("/err")', - errors: createErrors(), + errors: 1, }, { code: 'import err from "./err"', output: 'import error from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import err, {foo as bar} from "./err"', output: 'import error, {foo as bar} from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import {default as err, foo as bar} from "./err"', output: 'import {default as error, foo as bar} from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import * as err from "./err"', output: 'import * as error from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import foo, * as err from "./err"', output: 'import foo, * as error from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import {err} from "./err"', output: 'import {err as error} from "./err"', - errors: createErrors(), + errors: 1, }, { code: 'import {default as foo, err} from "./err"', output: 'import {default as foo, err as error} from "./err"', - errors: createErrors(), + errors: 1, }, { @@ -1590,7 +1553,7 @@ const importExportTests = { let error; export {error as err}; `, - errors: createErrors(), + errors: 1, }, { @@ -1602,28 +1565,28 @@ const importExportTests = { let error; export {error as err}; `, - errors: createErrors(), + errors: 1, }, { code: 'export const err = {}', - errors: createErrors(), + errors: 1, }, { code: 'export let err', - errors: createErrors(), + errors: 1, }, { code: 'export var err', - errors: createErrors(), + errors: 1, }, { code: 'export function err() {}', - errors: createErrors(), + errors: 1, }, { code: 'export class err {}', - errors: createErrors(), + errors: 1, }, { @@ -1635,7 +1598,7 @@ const importExportTests = { const error_ = {}; export const error = error_; `, - errors: createErrors(), + errors: 1, }, { @@ -1647,7 +1610,7 @@ const importExportTests = { class error {}; console.log(error); `, - errors: createErrors(), + errors: 1, }, { @@ -1667,7 +1630,7 @@ const importExportTests = { }; console.log(error); `, - errors: createErrors(), + errors: 1, }, { @@ -1676,7 +1639,7 @@ const importExportTests = { export {foo as err}; `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, ], @@ -1724,24 +1687,24 @@ test.babel({ return property; } `, - errors: createErrors(), + errors: 1, }, { code: '({err}) => err;', output: '({err: error}) => error;', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'err => ({err});', output: 'error => ({err: error});', options: customOptions, - errors: createErrors(), + errors: 1, }, { code: 'Foo.customProps = {}', options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1750,7 +1713,7 @@ test.babel({ } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { code: outdent` @@ -1759,7 +1722,7 @@ test.babel({ } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, { @@ -1779,17 +1742,25 @@ test.babel({ } `, options: checkPropertiesOptions, - errors: createErrors(), + errors: 1, }, ], }); test.typescript({ + testerOptions: { + languageOptions: { + globals: { + document: 'readonly', + event: 'readonly', + Response: 'readonly', + }, + }, + }, valid: [], invalid: [ // Types ...[ - 'declare const prop: string;', 'declare const prop: string;', 'declare var prop: number;', 'declare let prop: any;', @@ -1798,14 +1769,14 @@ test.typescript({ ].map(code => ({ code, output: code.replace('prop', 'property'), - errors: createErrors(), + errors: 1, })), // #763 { code: 'const foo = (extraParams?: string) => {}', output: 'const foo = (extraParameters?: string) => {}', - errors: createErrors(), + errors: 1, }, { code: 'const foo = (extr\u0061Params ? : string) => {}', @@ -1839,7 +1810,7 @@ test.typescript({ // #1102 { code: 'export type Props = string', - errors: createErrors(), + errors: 1, }, // #347 @@ -1864,7 +1835,7 @@ test.typescript({ }, }, ], - errors: createErrors(), + errors: 1, }, // https://github.com/facebook/relay/blob/597d2a17aa29d401830407b6814a5f8d148f632d/packages/relay-experimental/EntryPointTypes.flow.js#L138 @@ -1875,7 +1846,7 @@ test.typescript({ output: outdent` export type PreloadProps = {} `, - errors: [...createErrors(), ...createErrors()], + errors: 2, }, // https://github.com/facebook/relay/blob/597d2a17aa29d401830407b6814a5f8d148f632d/packages/relay-experimental/EntryPointTypes.flow.js#L138 @@ -1886,7 +1857,7 @@ test.typescript({ output: outdent` export type PreloadProps = {}; `, - errors: [...createErrors(), ...createErrors()], + errors: 2, }, ], }); @@ -1894,9 +1865,11 @@ test.typescript({ // JSX test.typescript({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -1931,6 +1904,10 @@ test({ code: 'foo();', filename: 'err/http-error.js', }, + { + code: 'foo();', + filename: 'МiΡ€ΡŠ.html', + }, // `ignore` option { code: outdent` @@ -1944,7 +1921,7 @@ test({ /^e_/, // eslint-disable-next-line prefer-regex-literals new RegExp('_e$', 'i'), - '\\.e2e\\.', + String.raw`\.e2e\.`, ], }, ], @@ -1954,12 +1931,12 @@ test({ { code: 'foo();', filename: 'err/http-err.js', - errors: createErrors(), + errors: 1, }, { code: 'foo();', filename: 'http-err.js', - errors: createErrors(), + errors: 1, }, { code: 'foo();', diff --git a/test/relative-url-style.mjs b/test/relative-url-style.mjs index f567726c8c..1c896312de 100644 --- a/test/relative-url-style.mjs +++ b/test/relative-url-style.mjs @@ -28,8 +28,8 @@ test.snapshot({ // We don't check cooked value 'new URL(`\\u002E/${foo}`, base)', // We don't check escaped string - 'new URL("\\u002E/foo", base)', - 'new URL(\'\\u002E/foo\', base)', + String.raw`new URL("\u002E/foo", base)`, + String.raw`new URL('\u002E/foo', base)`, ], invalid: [ 'new URL("./foo", base)', @@ -67,9 +67,9 @@ test.snapshot({ 'new URL("/foo", base)', 'new URL("../foo", base)', 'new URL(".././foo", base)', - 'new URL("C:\\foo", base)', - 'new URL("\\u002E/foo", base)', - 'new URL("\\u002Ffoo", base)', + String.raw`new URL("C:\foo", base)`, + String.raw`new URL("\u002E/foo", base)`, + String.raw`new URL("\u002Ffoo", base)`, ].map(code => ({code, options: alwaysAddDotSlashOptions})), invalid: [ 'new URL("foo", base)', diff --git a/test/run-rules-on-codebase/lint.mjs b/test/run-rules-on-codebase/lint.mjs index 643b5b27c8..bf72648400 100644 --- a/test/run-rules-on-codebase/lint.mjs +++ b/test/run-rules-on-codebase/lint.mjs @@ -23,20 +23,17 @@ const { }); const configs = [ - // TODO: Use `eslintPluginUnicorn.configs.all` instead when we change preset to flat config + eslintPluginUnicorn.configs['flat/all'], { - plugins: { - unicorn: eslintPluginUnicorn, + linterOptions: { + reportUnusedDisableDirectives: false, }, - rules: eslintPluginUnicorn.configs.all.rules, }, { ignores: [ 'coverage', 'test/integration/fixtures', 'test/integration/fixtures-local', - // Ignore this file self temporarily, disabling `n/file-extension-in-import` comment cause error - 'test/run-rules-on-codebase/lint.mjs', 'rules/utils/lodash.js', ], }, @@ -44,15 +41,14 @@ const configs = [ rules: { // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1109#issuecomment-782689255 'unicorn/consistent-destructuring': 'off', + // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2341 + 'unicorn/escape-case': 'off', + 'unicorn/no-hex-escape': 'off', // Buggy 'unicorn/custom-error-definition': 'off', 'unicorn/consistent-function-scoping': 'off', // Annoying 'unicorn/no-keyword-prefix': 'off', - 'unicorn/no-unsafe-regex': 'off', - // Not ready yet - 'unicorn/prefer-string-replace-all': 'off', - 'unicorn/prefer-at': 'off', }, }, { diff --git a/test/smoke/eslint-remote-tester.config.js b/test/smoke/eslint-remote-tester.config.js deleted file mode 100644 index 7dbfba9971..0000000000 --- a/test/smoke/eslint-remote-tester.config.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -const {getRepositories, getPathIgnorePattern} = require('eslint-remote-tester-repositories'); - -module.exports = { - /** Repositories to scan */ - repositories: getRepositories({randomize: true}), - - /** Optional pattern used to exclude paths */ - pathIgnorePattern: getPathIgnorePattern(), - - /** Extensions of files under scanning */ - extensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'vue'], - - /** Maximum amount of tasks ran concurrently */ - concurrentTasks: 3, - - /** Optional boolean flag used to enable caching of cloned repositories. For CIs it's ideal to disable caching. Defaults to true. */ - cache: false, - - /** Optional setting for log level. Valid values are verbose, info, warn, error. Defaults to verbose. */ - logLevel: 'info', - - /** ESLint configuration */ - eslintrc: { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - project: [], - }, - extends: ['plugin:unicorn/all'], - }, -}; diff --git a/test/smoke/eslint-remote-tester.config.mjs b/test/smoke/eslint-remote-tester.config.mjs new file mode 100644 index 0000000000..f9ca31aec0 --- /dev/null +++ b/test/smoke/eslint-remote-tester.config.mjs @@ -0,0 +1,63 @@ +import { + getRepositories, + getPathIgnorePattern, +} from 'eslint-remote-tester-repositories'; +import typescriptParser from '@typescript-eslint/parser'; +import vueParser from 'vue-eslint-parser'; +import eslintPluginUnicorn from '../../index.js'; + +/** @type {import('eslint-remote-tester').Config} */ +const config = { + /** Repositories to scan */ + repositories: getRepositories({randomize: true}), + + /** Optional pattern used to exclude paths */ + pathIgnorePattern: getPathIgnorePattern(), + + /** Extensions of files under scanning */ + extensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'vue'], + + /** Maximum amount of tasks ran concurrently */ + concurrentTasks: 3, + + /** Optional boolean flag used to enable caching of cloned repositories. For CIs it's ideal to disable caching. Defaults to true. */ + cache: false, + + /** Optional setting for log level. Valid values are verbose, info, warn, error. Defaults to verbose. */ + logLevel: 'info', + + /** ESLint configuration */ + eslintConfig: [ + eslintPluginUnicorn.configs['flat/all'], + { + rules: { + // This rule crashing on replace string inside `jsx` or `Unicode escape sequence` + 'unicorn/string-content': 'off', + }, + }, + { + files: ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.tsx'], + languageOptions: { + parser: typescriptParser, + parserOptions: { + project: [], + }, + }, + }, + { + files: ['**/*.vue'], + languageOptions: { + parser: vueParser, + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaFeatures: { + jsx: true, + }, + project: [], + }, + }, + }, + ], +}; + +export default config; diff --git a/test/snapshots/consistent-empty-array-spread.mjs.md b/test/snapshots/consistent-empty-array-spread.mjs.md new file mode 100644 index 0000000000..c52617c284 --- /dev/null +++ b/test/snapshots/consistent-empty-array-spread.mjs.md @@ -0,0 +1,206 @@ +# Snapshot report for `test/consistent-empty-array-spread.mjs` + +The actual snapshot is saved in `consistent-empty-array-spread.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): [ ...(test ? [] : "ab"), ...(test ? "ab" : []), ]; + +> Input + + `␊ + 1 | [␊ + 2 | ...(test ? [] : "ab"),␊ + 3 | ...(test ? "ab" : []),␊ + 4 | ];␊ + ` + +> Output + + `␊ + 1 | [␊ + 2 | ...(test ? '' : "ab"),␊ + 3 | ...(test ? "ab" : ''),␊ + 4 | ];␊ + ` + +> Error 1/2 + + `␊ + 1 | [␊ + > 2 | ...(test ? [] : "ab"),␊ + | ^^ Prefer using empty string since the alternate is a string.␊ + 3 | ...(test ? "ab" : []),␊ + 4 | ];␊ + ` + +> Error 2/2 + + `␊ + 1 | [␊ + 2 | ...(test ? [] : "ab"),␊ + > 3 | ...(test ? "ab" : []),␊ + | ^^ Prefer using empty string since the consequent is a string.␊ + 4 | ];␊ + ` + +## invalid(2): const STRING = "ab"; [ ...(test ? [] : STRING), ...(test ? STRING : []), ]; + +> Input + + `␊ + 1 | const STRING = "ab";␊ + 2 | [␊ + 3 | ...(test ? [] : STRING),␊ + 4 | ...(test ? STRING : []),␊ + 5 | ];␊ + ` + +> Output + + `␊ + 1 | const STRING = "ab";␊ + 2 | [␊ + 3 | ...(test ? '' : STRING),␊ + 4 | ...(test ? STRING : ''),␊ + 5 | ];␊ + ` + +> Error 1/2 + + `␊ + 1 | const STRING = "ab";␊ + 2 | [␊ + > 3 | ...(test ? [] : STRING),␊ + | ^^ Prefer using empty string since the alternate is a string.␊ + 4 | ...(test ? STRING : []),␊ + 5 | ];␊ + ` + +> Error 2/2 + + `␊ + 1 | const STRING = "ab";␊ + 2 | [␊ + 3 | ...(test ? [] : STRING),␊ + > 4 | ...(test ? STRING : []),␊ + | ^^ Prefer using empty string since the consequent is a string.␊ + 5 | ];␊ + ` + +## invalid(3): [ ...(test ? "" : [a, b]), ...(test ? [a, b] : ""), ]; + +> Input + + `␊ + 1 | [␊ + 2 | ...(test ? "" : [a, b]),␊ + 3 | ...(test ? [a, b] : ""),␊ + 4 | ];␊ + ` + +> Output + + `␊ + 1 | [␊ + 2 | ...(test ? [] : [a, b]),␊ + 3 | ...(test ? [a, b] : []),␊ + 4 | ];␊ + ` + +> Error 1/2 + + `␊ + 1 | [␊ + > 2 | ...(test ? "" : [a, b]),␊ + | ^^ Prefer using empty array since the alternate is an array.␊ + 3 | ...(test ? [a, b] : ""),␊ + 4 | ];␊ + ` + +> Error 2/2 + + `␊ + 1 | [␊ + 2 | ...(test ? "" : [a, b]),␊ + > 3 | ...(test ? [a, b] : ""),␊ + | ^^ Prefer using empty array since the consequent is an array.␊ + 4 | ];␊ + ` + +## invalid(4): const ARRAY = ["a", "b"]; [ /* hole */, ...(test ? "" : ARRAY), /* hole */, ...(test ? ARRAY : ""), /* hole */, ]; + +> Input + + `␊ + 1 | const ARRAY = ["a", "b"];␊ + 2 | [␊ + 3 | /* hole */,␊ + 4 | ...(test ? "" : ARRAY),␊ + 5 | /* hole */,␊ + 6 | ...(test ? ARRAY : ""),␊ + 7 | /* hole */,␊ + 8 | ];␊ + ` + +> Output + + `␊ + 1 | const ARRAY = ["a", "b"];␊ + 2 | [␊ + 3 | /* hole */,␊ + 4 | ...(test ? [] : ARRAY),␊ + 5 | /* hole */,␊ + 6 | ...(test ? ARRAY : []),␊ + 7 | /* hole */,␊ + 8 | ];␊ + ` + +> Error 1/2 + + `␊ + 1 | const ARRAY = ["a", "b"];␊ + 2 | [␊ + 3 | /* hole */,␊ + > 4 | ...(test ? "" : ARRAY),␊ + | ^^ Prefer using empty array since the alternate is an array.␊ + 5 | /* hole */,␊ + 6 | ...(test ? ARRAY : ""),␊ + 7 | /* hole */,␊ + 8 | ];␊ + ` + +> Error 2/2 + + `␊ + 1 | const ARRAY = ["a", "b"];␊ + 2 | [␊ + 3 | /* hole */,␊ + 4 | ...(test ? "" : ARRAY),␊ + 5 | /* hole */,␊ + > 6 | ...(test ? ARRAY : ""),␊ + | ^^ Prefer using empty array since the consequent is an array.␊ + 7 | /* hole */,␊ + 8 | ];␊ + ` + +## invalid(5): [...(foo ? "" : [])] + +> Input + + `␊ + 1 | [...(foo ? "" : [])]␊ + ` + +> Output + + `␊ + 1 | [...(foo ? [] : [])]␊ + ` + +> Error 1/1 + + `␊ + > 1 | [...(foo ? "" : [])]␊ + | ^^ Prefer using empty array since the alternate is an array.␊ + ` diff --git a/test/snapshots/consistent-empty-array-spread.mjs.snap b/test/snapshots/consistent-empty-array-spread.mjs.snap new file mode 100644 index 0000000000..c4ad96bdf1 Binary files /dev/null and b/test/snapshots/consistent-empty-array-spread.mjs.snap differ diff --git a/test/snapshots/error-message.mjs.md b/test/snapshots/error-message.mjs.md index 11e4c907ce..57b3116ac7 100644 --- a/test/snapshots/error-message.mjs.md +++ b/test/snapshots/error-message.mjs.md @@ -286,6 +286,21 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^^^ Pass a message to the \`RangeError\` constructor.␊ ` +## invalid(19): throw Object.assign(new Error(), {foo}) + +> Input + + `␊ + 1 | throw Object.assign(new Error(), {foo})␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw Object.assign(new Error(), {foo})␊ + | ^^^^^^^^^^^ Pass a message to the \`Error\` constructor.␊ + ` + ## invalid(1): new AggregateError(errors) > Input diff --git a/test/snapshots/error-message.mjs.snap b/test/snapshots/error-message.mjs.snap index 60ff9c14e0..0345896652 100644 Binary files a/test/snapshots/error-message.mjs.snap and b/test/snapshots/error-message.mjs.snap differ diff --git a/test/snapshots/new-for-builtins.mjs.md b/test/snapshots/new-for-builtins.mjs.md index df6ce0dce8..196f2cb928 100644 --- a/test/snapshots/new-for-builtins.mjs.md +++ b/test/snapshots/new-for-builtins.mjs.md @@ -1267,105 +1267,124 @@ Generated by [AVA](https://avajs.dev). > Input `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - 13 | return Array()␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - 19 | return Map()␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + 12 | return Array()␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + 18 | return Map()␊ + 19 | }␊ ` > Output `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - 13 | return new Array()␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - 19 | return new Map()␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + 12 | return new Array()␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + 18 | return new Map()␊ + 19 | }␊ ` > Error 1/2 `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - > 13 | return Array()␊ - | ^^^^^^^ Use \`new Array()\` instead of \`Array()\`.␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - 19 | return Map()␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + > 12 | return Array()␊ + | ^^^^^^^ Use \`new Array()\` instead of \`Array()\`.␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + 18 | return Map()␊ + 19 | }␊ ` > Error 2/2 `␊ - 1 |␊ - 2 | function varCheck() {␊ - 3 | {␊ - 4 | var WeakMap = function() {};␊ - 5 | }␊ - 6 | // This should not reported␊ - 7 | return WeakMap()␊ - 8 | }␊ - 9 | function constCheck() {␊ - 10 | {␊ - 11 | const Array = function() {};␊ - 12 | }␊ - 13 | return Array()␊ - 14 | }␊ - 15 | function letCheck() {␊ - 16 | {␊ - 17 | let Map = function() {};␊ - 18 | }␊ - > 19 | return Map()␊ - | ^^^^^ Use \`new Map()\` instead of \`Map()\`.␊ - 20 | }␊ - 21 | ␊ + 1 | function varCheck() {␊ + 2 | {␊ + 3 | var WeakMap = function() {};␊ + 4 | }␊ + 5 | // This should not reported␊ + 6 | return WeakMap()␊ + 7 | }␊ + 8 | function constCheck() {␊ + 9 | {␊ + 10 | const Array = function() {};␊ + 11 | }␊ + 12 | return Array()␊ + 13 | }␊ + 14 | function letCheck() {␊ + 15 | {␊ + 16 | let Map = function() {};␊ + 17 | }␊ + > 18 | return Map()␊ + | ^^^^^ Use \`new Map()\` instead of \`Map()\`.␊ + 19 | }␊ + ` + +## invalid(60): function foo() { return(globalThis).Map() } + +> Input + + `␊ + 1 | function foo() {␊ + 2 | return(globalThis).Map()␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | return new (globalThis).Map()␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | return(globalThis).Map()␊ + | ^^^^^^^^^^^^^^^^^^ Use \`new Map()\` instead of \`Map()\`.␊ + 3 | }␊ ` diff --git a/test/snapshots/new-for-builtins.mjs.snap b/test/snapshots/new-for-builtins.mjs.snap index 51412c8f5e..c47a5dd2d2 100644 Binary files a/test/snapshots/new-for-builtins.mjs.snap and b/test/snapshots/new-for-builtins.mjs.snap differ diff --git a/test/snapshots/no-array-method-this-argument.mjs.md b/test/snapshots/no-array-method-this-argument.mjs.md index 9691c03253..aa1fc0514d 100644 --- a/test/snapshots/no-array-method-this-argument.mjs.md +++ b/test/snapshots/no-array-method-this-argument.mjs.md @@ -214,7 +214,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.from()\`.␊ ` -## invalid(11): array.map(() => {}, thisArgument,) +## invalid(11): Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument)␊ + ` + +> Output + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, () => {})␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ` + +## invalid(12): array.map(() => {}, thisArgument,) > Input @@ -235,7 +256,7 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array#map()\`.␊ ` -## invalid(12): array.map(() => {}, (0, thisArgument),) +## invalid(13): array.map(() => {}, (0, thisArgument),) > Input @@ -256,7 +277,7 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array#map()\`.␊ ` -## invalid(13): Array.from(iterableOrArrayLike, () => {}, thisArgument,) +## invalid(14): Array.from(iterableOrArrayLike, () => {}, thisArgument,) > Input @@ -277,7 +298,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.from()\`.␊ ` -## invalid(14): array.map(() => {}, thisArgumentHasSideEffect()) +## invalid(15): Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument,) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument,)␊ + ` + +> Output + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, () => {},)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, () => {}, thisArgument,)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ` + +## invalid(16): array.map(() => {}, thisArgumentHasSideEffect()) > Input @@ -296,7 +338,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map(() => {})␊ ` -## invalid(15): Array.from(iterableOrArrayLike, () => {}, thisArgumentHasSideEffect()) +## invalid(17): Array.from(iterableOrArrayLike, () => {}, thisArgumentHasSideEffect()) > Input @@ -315,6 +357,25 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, () => {})␊ ` +## invalid(18): Array.fromAsync(iterableOrArrayLike, () => {}, thisArgumentHasSideEffect()) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, () => {}, thisArgumentHasSideEffect())␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, () => {}, thisArgumentHasSideEffect())␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, () => {})␊ + ` + ## invalid(1): array.map(callback, thisArgument) > Input @@ -361,7 +422,30 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, callback.bind(thisArgument))␊ ` -## invalid(3): array.map(callback, (0, thisArgument)) +## invalid(3): Array.fromAsync(iterableOrArrayLike, callback, thisArgument) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback, thisArgument)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, callback, thisArgument)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback)␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback.bind(thisArgument))␊ + ` + +## invalid(4): array.map(callback, (0, thisArgument)) > Input @@ -384,7 +468,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map(callback.bind((0, thisArgument)))␊ ` -## invalid(4): Array.from(iterableOrArrayLike, callback, (0, thisArgument)) +## invalid(5): Array.from(iterableOrArrayLike, callback, (0, thisArgument)) > Input @@ -407,7 +491,30 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, callback.bind((0, thisArgument)))␊ ` -## invalid(5): array.map(function () {}, thisArgument) +## invalid(6): Array.fromAsync(iterableOrArrayLike, callback, (0, thisArgument)) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback, (0, thisArgument))␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, callback, (0, thisArgument))␊ + | ^^^^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback)␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback.bind((0, thisArgument)))␊ + ` + +## invalid(7): array.map(function () {}, thisArgument) > Input @@ -430,7 +537,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map(function () {}.bind(thisArgument))␊ ` -## invalid(6): Array.from(iterableOrArrayLike, function () {}, thisArgument) +## invalid(8): Array.from(iterableOrArrayLike, function () {}, thisArgument) > Input @@ -453,7 +560,7 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, function () {}.bind(thisArgument))␊ ` -## invalid(7): array.map(function callback () {}, thisArgument) +## invalid(9): array.map(function callback () {}, thisArgument) > Input @@ -476,7 +583,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map(function callback () {}.bind(thisArgument))␊ ` -## invalid(8): Array.from(iterableOrArrayLike, function callback () {}, thisArgument) +## invalid(10): Array.from(iterableOrArrayLike, function callback () {}, thisArgument) > Input @@ -499,7 +606,30 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, function callback () {}.bind(thisArgument))␊ ` -## invalid(9): array.map( foo as bar, (( thisArgument )),) +## invalid(11): Array.fromAsync(iterableOrArrayLike, function callback () {}, thisArgument) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, function callback () {}, thisArgument)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, function callback () {}, thisArgument)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, function callback () {})␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, function callback () {}.bind(thisArgument))␊ + ` + +## invalid(12): array.map( foo as bar, (( thisArgument )),) > Input @@ -522,7 +652,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map( (foo as bar).bind((( thisArgument ))),)␊ ` -## invalid(10): Array.from(iterableOrArrayLike, foo as bar, (( thisArgument )),) +## invalid(13): Array.from(iterableOrArrayLike, foo as bar, (( thisArgument )),) > Input @@ -545,7 +675,30 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, (foo as bar).bind((( thisArgument ))),)␊ ` -## invalid(11): array.map( (( foo as bar )), (( thisArgument )),) +## invalid(14): Array.fromAsync(iterableOrArrayLike, foo as bar, (( thisArgument )),) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, foo as bar, (( thisArgument )),)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, foo as bar, (( thisArgument )),)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, foo as bar,)␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, (foo as bar).bind((( thisArgument ))),)␊ + ` + +## invalid(15): array.map( (( foo as bar )), (( thisArgument )),) > Input @@ -568,7 +721,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map( (( foo as bar )).bind((( thisArgument ))),)␊ ` -## invalid(12): Array.from(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),) +## invalid(16): Array.from(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),) > Input @@ -591,7 +744,30 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, (( foo as bar )).bind((( thisArgument ))),)␊ ` -## invalid(13): array.map( (( 0, callback )), (( thisArgument )),) +## invalid(17): Array.fromAsync(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, (( foo as bar )), (( thisArgument )),)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, (( foo as bar )),)␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, (( foo as bar )).bind((( thisArgument ))),)␊ + ` + +## invalid(18): array.map( (( 0, callback )), (( thisArgument )),) > Input @@ -614,7 +790,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map( (( 0, callback )).bind((( thisArgument ))),)␊ ` -## invalid(14): Array.from(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),) +## invalid(19): Array.from(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),) > Input @@ -637,7 +813,30 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, (( 0, callback )).bind((( thisArgument ))),)␊ ` -## invalid(15): array.map((0, () => {}), thisArgument) +## invalid(20): Array.fromAsync(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, (( 0, callback )), (( thisArgument )),)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, (( 0, callback )),)␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, (( 0, callback )).bind((( thisArgument ))),)␊ + ` + +## invalid(21): array.map((0, () => {}), thisArgument) > Input @@ -660,7 +859,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map((0, () => {}).bind(thisArgument))␊ ` -## invalid(16): Array.from(iterableOrArrayLike, (0, () => {}), thisArgument) +## invalid(22): Array.from(iterableOrArrayLike, (0, () => {}), thisArgument) > Input @@ -683,7 +882,30 @@ Generated by [AVA](https://avajs.dev). 1 | Array.from(iterableOrArrayLike, (0, () => {}).bind(thisArgument))␊ ` -## invalid(17): array.map(callback.bind(foo), thisArgument) +## invalid(23): Array.fromAsync(iterableOrArrayLike, (0, () => {}), thisArgument) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, (0, () => {}), thisArgument)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, (0, () => {}), thisArgument)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, (0, () => {}))␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, (0, () => {}).bind(thisArgument))␊ + ` + +## invalid(24): array.map(callback.bind(foo), thisArgument) > Input @@ -706,7 +928,7 @@ Generated by [AVA](https://avajs.dev). 1 | array.map(callback.bind(foo).bind(thisArgument))␊ ` -## invalid(18): Array.from(iterableOrArrayLike, callback.bind(foo), thisArgument) +## invalid(25): Array.from(iterableOrArrayLike, callback.bind(foo), thisArgument) > Input @@ -728,3 +950,26 @@ Generated by [AVA](https://avajs.dev). Suggestion 2/2: Use a bound function.␊ 1 | Array.from(iterableOrArrayLike, callback.bind(foo).bind(thisArgument))␊ ` + +## invalid(26): Array.fromAsync(iterableOrArrayLike, callback.bind(foo), thisArgument) + +> Input + + `␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback.bind(foo), thisArgument)␊ + ` + +> Error 1/1 + + `␊ + > 1 | Array.fromAsync(iterableOrArrayLike, callback.bind(foo), thisArgument)␊ + | ^^^^^^^^^^^^ Do not use the \`this\` argument in \`Array.fromAsync()\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Remove this argument.␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback.bind(foo))␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Use a bound function.␊ + 1 | Array.fromAsync(iterableOrArrayLike, callback.bind(foo).bind(thisArgument))␊ + ` diff --git a/test/snapshots/no-array-method-this-argument.mjs.snap b/test/snapshots/no-array-method-this-argument.mjs.snap index fbad733253..4ea59be7bc 100644 Binary files a/test/snapshots/no-array-method-this-argument.mjs.snap and b/test/snapshots/no-array-method-this-argument.mjs.snap differ diff --git a/test/snapshots/no-invalid-fetch-options.mjs.md b/test/snapshots/no-invalid-fetch-options.mjs.md new file mode 100644 index 0000000000..263c6dfd06 --- /dev/null +++ b/test/snapshots/no-invalid-fetch-options.mjs.md @@ -0,0 +1,273 @@ +# Snapshot report for `test/no-invalid-fetch-options.mjs` + +The actual snapshot is saved in `no-invalid-fetch-options.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): fetch(url, {body}) + +> Input + + `␊ + 1 | fetch(url, {body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | fetch(url, {body})␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + ` + +## invalid(2): new Request(url, {body}) + +> Input + + `␊ + 1 | new Request(url, {body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | new Request(url, {body})␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + ` + +## invalid(3): fetch(url, {method: "GET", body}) + +> Input + + `␊ + 1 | fetch(url, {method: "GET", body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | fetch(url, {method: "GET", body})␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + ` + +## invalid(4): new Request(url, {method: "GET", body}) + +> Input + + `␊ + 1 | new Request(url, {method: "GET", body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | new Request(url, {method: "GET", body})␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + ` + +## invalid(5): fetch(url, {method: "HEAD", body}) + +> Input + + `␊ + 1 | fetch(url, {method: "HEAD", body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | fetch(url, {method: "HEAD", body})␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + ` + +## invalid(6): new Request(url, {method: "HEAD", body}) + +> Input + + `␊ + 1 | new Request(url, {method: "HEAD", body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | new Request(url, {method: "HEAD", body})␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + ` + +## invalid(7): fetch(url, {method: "head", body}) + +> Input + + `␊ + 1 | fetch(url, {method: "head", body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | fetch(url, {method: "head", body})␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + ` + +## invalid(8): new Request(url, {method: "head", body}) + +> Input + + `␊ + 1 | new Request(url, {method: "head", body})␊ + ` + +> Error 1/1 + + `␊ + > 1 | new Request(url, {method: "head", body})␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + ` + +## invalid(9): const method = "head"; new Request(url, {method, body: "foo=bar"}) + +> Input + + `␊ + 1 | const method = "head"; new Request(url, {method, body: "foo=bar"})␊ + ` + +> Error 1/1 + + `␊ + > 1 | const method = "head"; new Request(url, {method, body: "foo=bar"})␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + ` + +## invalid(10): const method = "head"; fetch(url, {method, body: "foo=bar"}) + +> Input + + `␊ + 1 | const method = "head"; fetch(url, {method, body: "foo=bar"})␊ + ` + +> Error 1/1 + + `␊ + > 1 | const method = "head"; fetch(url, {method, body: "foo=bar"})␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + ` + +## invalid(11): fetch(url, {body}, extraArgument) + +> Input + + `␊ + 1 | fetch(url, {body}, extraArgument)␊ + ` + +> Error 1/1 + + `␊ + > 1 | fetch(url, {body}, extraArgument)␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + ` + +## invalid(12): new Request(url, {body}, extraArgument) + +> Input + + `␊ + 1 | new Request(url, {body}, extraArgument)␊ + ` + +> Error 1/1 + + `␊ + > 1 | new Request(url, {body}, extraArgument)␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + ` + +## invalid(13): fetch(url, { body: undefined, body: 'foo=bar', }); + +> Input + + `␊ + 1 | fetch(url, {␊ + 2 | body: undefined,␊ + 3 | body: 'foo=bar',␊ + 4 | });␊ + ` + +> Error 1/1 + + `␊ + 1 | fetch(url, {␊ + 2 | body: undefined,␊ + > 3 | body: 'foo=bar',␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + 4 | });␊ + ` + +## invalid(14): new Request(url, { body: undefined, body: 'foo=bar', }); + +> Input + + `␊ + 1 | new Request(url, {␊ + 2 | body: undefined,␊ + 3 | body: 'foo=bar',␊ + 4 | });␊ + ` + +> Error 1/1 + + `␊ + 1 | new Request(url, {␊ + 2 | body: undefined,␊ + > 3 | body: 'foo=bar',␊ + | ^^^^ "body" is not allowed when method is "GET".␊ + 4 | });␊ + ` + +## invalid(15): fetch(url, { method: 'post', body: 'foo=bar', method: 'HEAD', }); + +> Input + + `␊ + 1 | fetch(url, {␊ + 2 | method: 'post',␊ + 3 | body: 'foo=bar',␊ + 4 | method: 'HEAD',␊ + 5 | });␊ + ` + +> Error 1/1 + + `␊ + 1 | fetch(url, {␊ + 2 | method: 'post',␊ + > 3 | body: 'foo=bar',␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + 4 | method: 'HEAD',␊ + 5 | });␊ + ` + +## invalid(16): new Request(url, { method: 'post', body: 'foo=bar', method: 'HEAD', }); + +> Input + + `␊ + 1 | new Request(url, {␊ + 2 | method: 'post',␊ + 3 | body: 'foo=bar',␊ + 4 | method: 'HEAD',␊ + 5 | });␊ + ` + +> Error 1/1 + + `␊ + 1 | new Request(url, {␊ + 2 | method: 'post',␊ + > 3 | body: 'foo=bar',␊ + | ^^^^ "body" is not allowed when method is "HEAD".␊ + 4 | method: 'HEAD',␊ + 5 | });␊ + ` diff --git a/test/snapshots/no-invalid-fetch-options.mjs.snap b/test/snapshots/no-invalid-fetch-options.mjs.snap new file mode 100644 index 0000000000..a41b2288ea Binary files /dev/null and b/test/snapshots/no-invalid-fetch-options.mjs.snap differ diff --git a/test/snapshots/no-magic-array-flat-depth.mjs.md b/test/snapshots/no-magic-array-flat-depth.mjs.md new file mode 100644 index 0000000000..570a6364bb --- /dev/null +++ b/test/snapshots/no-magic-array-flat-depth.mjs.md @@ -0,0 +1,65 @@ +# Snapshot report for `test/no-magic-array-flat-depth.mjs` + +The actual snapshot is saved in `no-magic-array-flat-depth.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): array.flat(2) + +> Input + + `␊ + 1 | array.flat(2)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.flat(2)␊ + | ^ Magic number as depth is not allowed.␊ + ` + +## invalid(2): array?.flat(2) + +> Input + + `␊ + 1 | array?.flat(2)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array?.flat(2)␊ + | ^ Magic number as depth is not allowed.␊ + ` + +## invalid(3): array.flat(99,) + +> Input + + `␊ + 1 | array.flat(99,)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.flat(99,)␊ + | ^^ Magic number as depth is not allowed.␊ + ` + +## invalid(4): array.flat(0b10,) + +> Input + + `␊ + 1 | array.flat(0b10,)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.flat(0b10,)␊ + | ^^^^ Magic number as depth is not allowed.␊ + ` diff --git a/test/snapshots/no-magic-array-flat-depth.mjs.snap b/test/snapshots/no-magic-array-flat-depth.mjs.snap new file mode 100644 index 0000000000..1b9af08994 Binary files /dev/null and b/test/snapshots/no-magic-array-flat-depth.mjs.snap differ diff --git a/test/snapshots/no-negation-in-equality-check.mjs.md b/test/snapshots/no-negation-in-equality-check.mjs.md new file mode 100644 index 0000000000..6888103721 --- /dev/null +++ b/test/snapshots/no-negation-in-equality-check.mjs.md @@ -0,0 +1,268 @@ +# Snapshot report for `test/no-negation-in-equality-check.mjs` + +The actual snapshot is saved in `no-negation-in-equality-check.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): !foo === bar + +> Input + + `␊ + 1 | !foo === bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo === bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo !== bar␊ + ` + +## invalid(2): !foo !== bar + +> Input + + `␊ + 1 | !foo !== bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo !== bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '===' check.␊ + 1 | foo === bar␊ + ` + +## invalid(3): !foo == bar + +> Input + + `␊ + 1 | !foo == bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo == bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!=' check.␊ + 1 | foo != bar␊ + ` + +## invalid(4): !foo != bar + +> Input + + `␊ + 1 | !foo != bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !foo != bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '==' check.␊ + 1 | foo == bar␊ + ` + +## invalid(5): function x() { return!foo === bar; } + +> Input + + `␊ + 1 | function x() {␊ + 2 | return!foo === bar;␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function x() {␊ + > 2 | return!foo === bar;␊ + | ^ Negated expression in not allowed in equality check.␊ + 3 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | function x() {␊ + 2 | return foo !== bar;␊ + 3 | }␊ + ` + +## invalid(6): function x() { return! foo === bar; throw! foo === bar; } + +> Input + + `␊ + 1 | function x() {␊ + 2 | return!␊ + 3 | foo === bar;␊ + 4 | throw!␊ + 5 | foo === bar;␊ + 6 | }␊ + ` + +> Error 1/2 + + `␊ + 1 | function x() {␊ + > 2 | return!␊ + | ^ Negated expression in not allowed in equality check.␊ + 3 | foo === bar;␊ + 4 | throw!␊ + 5 | foo === bar;␊ + 6 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | function x() {␊ + 2 | return (␊ + 3 | foo !== bar);␊ + 4 | throw!␊ + 5 | foo === bar;␊ + 6 | }␊ + ` + +> Error 2/2 + + `␊ + 1 | function x() {␊ + 2 | return!␊ + 3 | foo === bar;␊ + > 4 | throw!␊ + | ^ Negated expression in not allowed in equality check.␊ + 5 | foo === bar;␊ + 6 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | function x() {␊ + 2 | return!␊ + 3 | foo === bar;␊ + 4 | throw (␊ + 5 | foo !== bar);␊ + 6 | }␊ + ` + +## invalid(7): foo !(a) === b + +> Input + + `␊ + 1 | foo␊ + 2 | !(a) === b␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | !(a) === b␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ;(a) !== b␊ + ` + +## invalid(8): foo ![a, b].join('') === c + +> Input + + `␊ + 1 | foo␊ + 2 | ![a, b].join('') === c␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | ![a, b].join('') === c␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ;[a, b].join('') !== c␊ + ` + +## invalid(9): foo ! [a, b].join('') === c + +> Input + + `␊ + 1 | foo␊ + 2 | ! [a, b].join('') === c␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | ! [a, b].join('') === c␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ; [a, b].join('') !== c␊ + ` + +## invalid(10): foo !/* comment */[a, b].join('') === c + +> Input + + `␊ + 1 | foo␊ + 2 | !/* comment */[a, b].join('') === c␊ + ` + +> Error 1/1 + + `␊ + 1 | foo␊ + > 2 | !/* comment */[a, b].join('') === c␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | foo␊ + 2 | ;/* comment */[a, b].join('') !== c␊ + ` + +## invalid(11): !!foo === bar + +> Input + + `␊ + 1 | !!foo === bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | !!foo === bar␊ + | ^ Negated expression in not allowed in equality check.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to '!==' check.␊ + 1 | !foo !== bar␊ + ` diff --git a/test/snapshots/no-negation-in-equality-check.mjs.snap b/test/snapshots/no-negation-in-equality-check.mjs.snap new file mode 100644 index 0000000000..9df853b5dd Binary files /dev/null and b/test/snapshots/no-negation-in-equality-check.mjs.snap differ diff --git a/test/snapshots/prefer-array-some.mjs.md b/test/snapshots/prefer-array-some.mjs.md index 4bc92a86e3..d0a5d9a363 100644 --- a/test/snapshots/prefer-array-some.mjs.md +++ b/test/snapshots/prefer-array-some.mjs.md @@ -184,6 +184,342 @@ Generated by [AVA](https://avajs.dev). 15 | );␊ ` +## invalid(1): foo.findIndex(bar) !== -1 + +> Input + + `␊ + 1 | foo.findIndex(bar) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) !== -1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(2): foo.findLastIndex(bar) !== -1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) !== -1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(3): foo.findIndex(bar) != -1 + +> Input + + `␊ + 1 | foo.findIndex(bar) != -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) != -1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(4): foo.findLastIndex(bar) != -1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) != -1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) != -1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(5): foo.findIndex(bar) > - 1 + +> Input + + `␊ + 1 | foo.findIndex(bar) > - 1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) > - 1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(6): foo.findLastIndex(bar) > - 1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) > - 1␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) > - 1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(7): foo.findIndex(bar) === -1 + +> Input + + `␊ + 1 | foo.findIndex(bar) === -1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) === -1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(8): foo.findLastIndex(bar) === -1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) === -1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) === -1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(9): foo.findIndex(bar) == - 1 + +> Input + + `␊ + 1 | foo.findIndex(bar) == - 1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) == - 1␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(10): foo.findLastIndex(bar) == - 1 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) == - 1␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) == - 1␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(11): foo.findIndex(bar) >= 0 + +> Input + + `␊ + 1 | foo.findIndex(bar) >= 0␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) >= 0␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(12): foo.findLastIndex(bar) >= 0 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) >= 0␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) >= 0␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(13): foo.findIndex(bar) < 0 + +> Input + + `␊ + 1 | foo.findIndex(bar) < 0␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) < 0␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(14): foo.findLastIndex(bar) < 0 + +> Input + + `␊ + 1 | foo.findLastIndex(bar) < 0␊ + ` + +> Output + + `␊ + 1 | !foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findLastIndex(bar) < 0␊ + | ^^^^^^^^^^^^^ Prefer \`.some(…)\` over \`.findLastIndex(…)\`.␊ + ` + +## invalid(15): foo.findIndex(bar) !== (( - 1 )) + +> Input + + `␊ + 1 | foo.findIndex(bar) !== (( - 1 ))␊ + ` + +> Output + + `␊ + 1 | foo.some(bar) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(bar) !== (( - 1 ))␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + +## invalid(16): foo.findIndex(element => element.bar === 1) !== (( - 1 )) + +> Input + + `␊ + 1 | foo.findIndex(element => element.bar === 1) !== (( - 1 ))␊ + ` + +> Output + + `␊ + 1 | foo.some(element => element.bar === 1) ␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.findIndex(element => element.bar === 1) !== (( - 1 ))␊ + | ^^^^^^^^^ Prefer \`.some(…)\` over \`.findIndex(…)\`.␊ + ` + ## invalid(1): foo.find(fn) == null > Input diff --git a/test/snapshots/prefer-array-some.mjs.snap b/test/snapshots/prefer-array-some.mjs.snap index 67bff2893c..a7086be00a 100644 Binary files a/test/snapshots/prefer-array-some.mjs.snap and b/test/snapshots/prefer-array-some.mjs.snap differ diff --git a/test/snapshots/prefer-dom-node-remove.mjs.md b/test/snapshots/prefer-dom-node-remove.mjs.md new file mode 100644 index 0000000000..1644550416 --- /dev/null +++ b/test/snapshots/prefer-dom-node-remove.mjs.md @@ -0,0 +1,840 @@ +# Snapshot report for `test/prefer-dom-node-remove.mjs` + +The actual snapshot is saved in `prefer-dom-node-remove.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): parentNode.removeChild(foo) + +> Input + + `␊ + 1 | parentNode.removeChild(foo)␊ + ` + +> Output + + `␊ + 1 | foo.remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(foo)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(2): parentNode.removeChild(this) + +> Input + + `␊ + 1 | parentNode.removeChild(this)␊ + ` + +> Output + + `␊ + 1 | this.remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(this)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(3): parentNode.removeChild(some.node) + +> Input + + `␊ + 1 | parentNode.removeChild(some.node)␊ + ` + +> Output + + `␊ + 1 | some.node.remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(some.node)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(4): parentNode.removeChild(getChild()) + +> Input + + `␊ + 1 | parentNode.removeChild(getChild())␊ + ` + +> Output + + `␊ + 1 | getChild().remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(getChild())␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(5): parentNode.removeChild(lib.getChild()) + +> Input + + `␊ + 1 | parentNode.removeChild(lib.getChild())␊ + ` + +> Output + + `␊ + 1 | lib.getChild().remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(lib.getChild())␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(6): parentNode.removeChild((() => childNode)()) + +> Input + + `␊ + 1 | parentNode.removeChild((() => childNode)())␊ + ` + +> Output + + `␊ + 1 | (() => childNode)().remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild((() => childNode)())␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(7): async function foo () { parentNode.removeChild( await getChild() ); } + +> Input + + `␊ + 1 | async function foo () {␊ + 2 | parentNode.removeChild(␊ + 3 | await getChild()␊ + 4 | );␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | async function foo () {␊ + 2 | (await getChild()).remove();␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | async function foo () {␊ + > 2 | parentNode.removeChild(␊ + | ^^^^^^^^^^^^^^^^^^^^^^^␊ + > 3 | await getChild()␊ + | ^^^^^^^^^^^^^^^^^^␊ + > 4 | );␊ + | ^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + 5 | }␊ + ` + +## invalid(8): async function foo () { parentNode.removeChild( (await getChild()) ); } + +> Input + + `␊ + 1 | async function foo () {␊ + 2 | parentNode.removeChild(␊ + 3 | (await getChild())␊ + 4 | );␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | async function foo () {␊ + 2 | (await getChild()).remove();␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | async function foo () {␊ + > 2 | parentNode.removeChild(␊ + | ^^^^^^^^^^^^^^^^^^^^^^^␊ + > 3 | (await getChild())␊ + | ^^^^^^^^^^^^^^^^^^^^␊ + > 4 | );␊ + | ^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + 5 | }␊ + ` + +## invalid(9): parentNode.removeChild((0, child)) + +> Input + + `␊ + 1 | parentNode.removeChild((0, child))␊ + ` + +> Output + + `␊ + 1 | (0, child).remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild((0, child))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(10): parentNode.removeChild( ( (new Image)) ) + +> Input + + `␊ + 1 | parentNode.removeChild( ( (new Image)) )␊ + ` + +> Output + + `␊ + 1 | ( (new Image)).remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild( ( (new Image)) )␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(11): parentNode.removeChild( new Audio ) + +> Input + + `␊ + 1 | parentNode.removeChild( new Audio )␊ + ` + +> Output + + `␊ + 1 | (new Audio).remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild( new Audio )␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(12): const array = [] parentNode.removeChild([a, b, c].reduce(child => child, child)) + +> Input + + `␊ + 1 | const array = []␊ + 2 | parentNode.removeChild([a, b, c].reduce(child => child, child))␊ + ` + +> Output + + `␊ + 1 | const array = []␊ + 2 | ;[a, b, c].reduce(child => child, child).remove()␊ + ` + +> Error 1/1 + + `␊ + 1 | const array = []␊ + > 2 | parentNode.removeChild([a, b, c].reduce(child => child, child))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(13): async function foo () { const array = [] parentNode.removeChild( await getChild() ); } + +> Input + + `␊ + 1 | async function foo () {␊ + 2 | const array = []␊ + 3 | parentNode.removeChild(␊ + 4 | await getChild()␊ + 5 | );␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | async function foo () {␊ + 2 | const array = []␊ + 3 | ;(await getChild()).remove();␊ + 4 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | async function foo () {␊ + 2 | const array = []␊ + > 3 | parentNode.removeChild(␊ + | ^^^^^^^^^^^^^^^^^^^^^^^␊ + > 4 | await getChild()␊ + | ^^^^^^^^^^^^^^^^^^␊ + > 5 | );␊ + | ^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + 6 | }␊ + ` + +## invalid(14): async function foo () { const array = [] parentNode.removeChild( (0, childNode) ); } + +> Input + + `␊ + 1 | async function foo () {␊ + 2 | const array = []␊ + 3 | parentNode.removeChild(␊ + 4 | (0, childNode)␊ + 5 | );␊ + 6 | }␊ + ` + +> Output + + `␊ + 1 | async function foo () {␊ + 2 | const array = []␊ + 3 | ;(0, childNode).remove();␊ + 4 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | async function foo () {␊ + 2 | const array = []␊ + > 3 | parentNode.removeChild(␊ + | ^^^^^^^^^^^^^^^^^^^^^^^␊ + > 4 | (0, childNode)␊ + | ^^^^^^^^^^^^^^^^␊ + > 5 | );␊ + | ^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + 6 | }␊ + ` + +## invalid(15): if (parentNode.removeChild(foo)) {} + +> Input + + `␊ + 1 | if (parentNode.removeChild(foo)) {}␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (parentNode.removeChild(foo)) {}␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | if (foo.remove()) {}␊ + ` + +## invalid(16): var removed = parentNode.removeChild(child); + +> Input + + `␊ + 1 | var removed = parentNode.removeChild(child);␊ + ` + +> Error 1/1 + + `␊ + > 1 | var removed = parentNode.removeChild(child);␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | var removed = child.remove();␊ + ` + +## invalid(17): const foo = parentNode.removeChild(child); + +> Input + + `␊ + 1 | const foo = parentNode.removeChild(child);␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = parentNode.removeChild(child);␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | const foo = child.remove();␊ + ` + +## invalid(18): foo.bar(parentNode.removeChild(child)); + +> Input + + `␊ + 1 | foo.bar(parentNode.removeChild(child));␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.bar(parentNode.removeChild(child));␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | foo.bar(child.remove());␊ + ` + +## invalid(19): parentNode.removeChild(child) || "foo"; + +> Input + + `␊ + 1 | parentNode.removeChild(child) || "foo";␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(child) || "foo";␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | child.remove() || "foo";␊ + ` + +## invalid(20): parentNode.removeChild(child) + 0; + +> Input + + `␊ + 1 | parentNode.removeChild(child) + 0;␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(child) + 0;␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | child.remove() + 0;␊ + ` + +## invalid(21): +parentNode.removeChild(child); + +> Input + + `␊ + 1 | +parentNode.removeChild(child);␊ + ` + +> Error 1/1 + + `␊ + > 1 | +parentNode.removeChild(child);␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | +child.remove();␊ + ` + +## invalid(22): parentNode.removeChild(child) ? "foo" : "bar"; + +> Input + + `␊ + 1 | parentNode.removeChild(child) ? "foo" : "bar";␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode.removeChild(child) ? "foo" : "bar";␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | child.remove() ? "foo" : "bar";␊ + ` + +## invalid(23): if (parentNode.removeChild(child)) {} + +> Input + + `␊ + 1 | if (parentNode.removeChild(child)) {}␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (parentNode.removeChild(child)) {}␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | if (child.remove()) {}␊ + ` + +## invalid(24): const foo = [parentNode.removeChild(child)] + +> Input + + `␊ + 1 | const foo = [parentNode.removeChild(child)]␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = [parentNode.removeChild(child)]␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | const foo = [child.remove()]␊ + ` + +## invalid(25): const foo = { bar: parentNode.removeChild(child) } + +> Input + + `␊ + 1 | const foo = { bar: parentNode.removeChild(child) }␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = { bar: parentNode.removeChild(child) }␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | const foo = { bar: child.remove() }␊ + ` + +## invalid(26): function foo() { return parentNode.removeChild(child); } + +> Input + + `␊ + 1 | function foo() { return parentNode.removeChild(child); }␊ + ` + +> Error 1/1 + + `␊ + > 1 | function foo() { return parentNode.removeChild(child); }␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | function foo() { return child.remove(); }␊ + ` + +## invalid(27): const foo = () => { return parentElement.removeChild(child); } + +> Input + + `␊ + 1 | const foo = () => { return parentElement.removeChild(child); }␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = () => { return parentElement.removeChild(child); }␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | const foo = () => { return child.remove(); }␊ + ` + +## invalid(28): foo(bar = parentNode.removeChild(child)) + +> Input + + `␊ + 1 | foo(bar = parentNode.removeChild(child))␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo(bar = parentNode.removeChild(child))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | foo(bar = child.remove())␊ + ` + +## invalid(29): foo().removeChild(child) + +> Input + + `␊ + 1 | foo().removeChild(child)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo().removeChild(child)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | child.remove()␊ + ` + +## invalid(30): foo[doSomething()].removeChild(child) + +> Input + + `␊ + 1 | foo[doSomething()].removeChild(child)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo[doSomething()].removeChild(child)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | child.remove()␊ + ` + +## invalid(31): parentNode?.removeChild(foo) + +> Input + + `␊ + 1 | parentNode?.removeChild(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | parentNode?.removeChild(foo)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode?.remove()\`.␊ + 1 | foo?.remove()␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | foo.remove()␊ + ` + +## invalid(32): foo?.parentNode.removeChild(foo) + +> Input + + `␊ + 1 | foo?.parentNode.removeChild(foo)␊ + ` + +> Output + + `␊ + 1 | foo?.remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo?.parentNode.removeChild(foo)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(33): foo.parentNode?.removeChild(foo) + +> Input + + `␊ + 1 | foo.parentNode?.removeChild(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.parentNode?.removeChild(foo)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode?.remove()\`.␊ + 1 | foo?.remove()␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | foo.remove()␊ + ` + +## invalid(34): foo?.parentNode?.removeChild(foo) + +> Input + + `␊ + 1 | foo?.parentNode?.removeChild(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo?.parentNode?.removeChild(foo)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode?.remove()\`.␊ + 1 | foo?.remove()␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | foo.remove()␊ + ` + +## invalid(35): foo.bar?.parentNode.removeChild(foo.bar) + +> Input + + `␊ + 1 | foo.bar?.parentNode.removeChild(foo.bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.bar?.parentNode.removeChild(foo.bar)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode?.remove()\`.␊ + 1 | foo.bar?.remove()␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | foo.bar.remove()␊ + ` + +## invalid(36): a.b?.c.parentNode.removeChild(foo) + +> Input + + `␊ + 1 | a.b?.c.parentNode.removeChild(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | a.b?.c.parentNode.removeChild(foo)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode?.remove()\`.␊ + 1 | foo?.remove()␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | foo.remove()␊ + ` + +## invalid(37): a[b?.c].parentNode.removeChild(foo) + +> Input + + `␊ + 1 | a[b?.c].parentNode.removeChild(foo)␊ + ` + +> Output + + `␊ + 1 | foo.remove()␊ + ` + +> Error 1/1 + + `␊ + > 1 | a[b?.c].parentNode.removeChild(foo)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ` + +## invalid(38): a?.b.parentNode.removeChild(a.b) + +> Input + + `␊ + 1 | a?.b.parentNode.removeChild(a.b)␊ + ` + +> Error 1/1 + + `␊ + > 1 | a?.b.parentNode.removeChild(a.b)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`childNode.remove()\` over \`parentNode.removeChild(childNode)\`.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode?.remove()\`.␊ + 1 | a.b?.remove()␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 2/2: Replace \`parentNode.removeChild(childNode)\` with \`childNode.remove()\`.␊ + 1 | a.b.remove()␊ + ` diff --git a/test/snapshots/prefer-dom-node-remove.mjs.snap b/test/snapshots/prefer-dom-node-remove.mjs.snap new file mode 100644 index 0000000000..d4a3697ff8 Binary files /dev/null and b/test/snapshots/prefer-dom-node-remove.mjs.snap differ diff --git a/test/snapshots/prefer-includes.mjs.md b/test/snapshots/prefer-includes.mjs.md index 92011629b7..97207ce93e 100644 --- a/test/snapshots/prefer-includes.mjs.md +++ b/test/snapshots/prefer-includes.mjs.md @@ -25,7 +25,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(2): str.indexOf('foo') != -1 +## invalid(2): 'foobar'.lastIndexOf('foo') !== -1 + +> Input + + `␊ + 1 | 'foobar'.lastIndexOf('foo') !== -1␊ + ` + +> Output + + `␊ + 1 | 'foobar'.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | 'foobar'.lastIndexOf('foo') !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(3): str.indexOf('foo') != -1 > Input @@ -46,7 +67,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(3): str.indexOf('foo') > -1 +## invalid(4): str.lastIndexOf('foo') != -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') != -1␊ + ` + +> Output + + `␊ + 1 | str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') != -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(5): str.indexOf('foo') > -1 > Input @@ -67,7 +109,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(4): str.indexOf('foo') == -1 +## invalid(6): str.lastIndexOf('foo') > -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') > -1␊ + ` + +> Output + + `␊ + 1 | str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') > -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(7): str.indexOf('foo') == -1 > Input @@ -88,7 +151,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(5): 'foobar'.indexOf('foo') >= 0 +## invalid(8): str.lastIndexOf('foo') == -1 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') == -1␊ + ` + +> Output + + `␊ + 1 | !str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') == -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(9): 'foobar'.indexOf('foo') >= 0 > Input @@ -109,7 +193,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(6): [1,2,3].indexOf(4) !== -1 +## invalid(10): 'foobar'.lastIndexOf('foo') >= 0 + +> Input + + `␊ + 1 | 'foobar'.lastIndexOf('foo') >= 0␊ + ` + +> Output + + `␊ + 1 | 'foobar'.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | 'foobar'.lastIndexOf('foo') >= 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(11): [1,2,3].indexOf(4) !== -1 > Input @@ -130,7 +235,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(7): str.indexOf('foo') < 0 +## invalid(12): [1,2,3].lastIndexOf(4) !== -1 + +> Input + + `␊ + 1 | [1,2,3].lastIndexOf(4) !== -1␊ + ` + +> Output + + `␊ + 1 | [1,2,3].includes(4)␊ + ` + +> Error 1/1 + + `␊ + > 1 | [1,2,3].lastIndexOf(4) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(13): str.indexOf('foo') < 0 > Input @@ -151,7 +277,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(8): ''.indexOf('foo') < 0 +## invalid(14): str.lastIndexOf('foo') < 0 + +> Input + + `␊ + 1 | str.lastIndexOf('foo') < 0␊ + ` + +> Output + + `␊ + 1 | !str.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | str.lastIndexOf('foo') < 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(15): ''.indexOf('foo') < 0 > Input @@ -172,7 +319,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(9): (a || b).indexOf('foo') === -1 +## invalid(16): ''.lastIndexOf('foo') < 0 + +> Input + + `␊ + 1 | ''.lastIndexOf('foo') < 0␊ + ` + +> Output + + `␊ + 1 | !''.includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | ''.lastIndexOf('foo') < 0␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(17): (a || b).indexOf('foo') === -1 > Input @@ -193,7 +361,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(10): foo.indexOf(bar, 0) !== -1 +## invalid(18): (a || b).lastIndexOf('foo') === -1 + +> Input + + `␊ + 1 | (a || b).lastIndexOf('foo') === -1␊ + ` + +> Output + + `␊ + 1 | !(a || b).includes('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | (a || b).lastIndexOf('foo') === -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(19): foo.indexOf(bar, 0) !== -1 > Input @@ -214,7 +403,28 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` -## invalid(11): foo.indexOf(bar, 1) !== -1 +## invalid(20): foo.lastIndexOf(bar, 0) !== -1 + +> Input + + `␊ + 1 | foo.lastIndexOf(bar, 0) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.includes(bar)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.lastIndexOf(bar, 0) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + +## invalid(21): foo.indexOf(bar, 1) !== -1 > Input @@ -235,6 +445,27 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^ Use \`.includes()\`, rather than \`.indexOf()\`, when checking for existence.␊ ` +## invalid(22): foo.lastIndexOf(bar, 1) !== -1 + +> Input + + `␊ + 1 | foo.lastIndexOf(bar, 1) !== -1␊ + ` + +> Output + + `␊ + 1 | foo.includes(bar, 1)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo.lastIndexOf(bar, 1) !== -1␊ + | ^^^^^^^^^^^ Use \`.includes()\`, rather than \`.lastIndexOf()\`, when checking for existence.␊ + ` + ## invalid(1): values.some(x => x === "foo") > Input diff --git a/test/snapshots/prefer-includes.mjs.snap b/test/snapshots/prefer-includes.mjs.snap index dd84e89630..b5991d9658 100644 Binary files a/test/snapshots/prefer-includes.mjs.snap and b/test/snapshots/prefer-includes.mjs.snap differ diff --git a/test/snapshots/prefer-module.mjs.md b/test/snapshots/prefer-module.mjs.md index 72140a0559..74f0633280 100644 --- a/test/snapshots/prefer-module.mjs.md +++ b/test/snapshots/prefer-module.mjs.md @@ -146,7 +146,7 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^ Do not use "__filename".␊ ␊ --------------------------------------------------------------------------------␊ - Suggestion 1/2: Replace \`__dirname\` with \`import.meta.filename\`.␊ + Suggestion 1/2: Replace \`__filename\` with \`import.meta.filename\`.␊ 1 | const dirname = import.meta.filename;␊ ␊ --------------------------------------------------------------------------------␊ @@ -192,7 +192,7 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^ Do not use "__filename".␊ ␊ --------------------------------------------------------------------------------␊ - Suggestion 1/2: Replace \`__dirname\` with \`import.meta.filename\`.␊ + Suggestion 1/2: Replace \`__filename\` with \`import.meta.filename\`.␊ 1 | const foo = {__filename: import.meta.filename, };␊ ␊ --------------------------------------------------------------------------------␊ @@ -238,7 +238,7 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^ Do not use "__filename".␊ ␊ --------------------------------------------------------------------------------␊ - Suggestion 1/2: Replace \`__dirname\` with \`import.meta.filename\`.␊ + Suggestion 1/2: Replace \`__filename\` with \`import.meta.filename\`.␊ 1 | if (import.meta.filename.endsWith(".js")) {}␊ ␊ --------------------------------------------------------------------------------␊ @@ -1857,7 +1857,7 @@ Generated by [AVA](https://avajs.dev). | ^^^^^^^^^^ Do not use "__filename".␊ ␊ --------------------------------------------------------------------------------␊ - Suggestion 1/2: Replace \`__dirname\` with \`import.meta.filename\`.␊ + Suggestion 1/2: Replace \`__filename\` with \`import.meta.filename\`.␊ 1 | import.meta.filename␊ ␊ --------------------------------------------------------------------------------␊ diff --git a/test/snapshots/prefer-module.mjs.snap b/test/snapshots/prefer-module.mjs.snap index cb289e3d22..062d9ab1a2 100644 Binary files a/test/snapshots/prefer-module.mjs.snap and b/test/snapshots/prefer-module.mjs.snap differ diff --git a/test/snapshots/prefer-number-properties.mjs.md b/test/snapshots/prefer-number-properties.mjs.md index 9f5072b1e6..659a392d88 100644 --- a/test/snapshots/prefer-number-properties.mjs.md +++ b/test/snapshots/prefer-number-properties.mjs.md @@ -117,6 +117,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -138,6 +148,16 @@ Generated by [AVA](https://avajs.dev). 1 | if (Number.isNaN(Infinity)) {}␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -159,6 +179,16 @@ Generated by [AVA](https://avajs.dev). 1 | if (Object.is(foo, Infinity)) {}␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -180,6 +210,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = bar[Infinity];␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -201,6 +241,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = {Infinity};␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -222,6 +272,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = {Infinity: Infinity};␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -243,6 +303,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = {[Infinity]: -Infinity};␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -271,6 +341,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = {[-Infinity]: Infinity};␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -299,6 +379,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = {Infinity: -Infinity};␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -320,6 +410,16 @@ Generated by [AVA](https://avajs.dev). 1 | const {foo = Infinity} = {};␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -341,6 +441,16 @@ Generated by [AVA](https://avajs.dev). 1 | const {foo = -Infinity} = {};␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -362,6 +472,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = Infinity.toString();␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -383,6 +503,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = -Infinity.toString();␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -404,6 +534,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = (-Infinity).toString();␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -425,6 +565,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = +Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -446,6 +596,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = +-Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -467,6 +627,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = -Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -488,6 +658,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = -(-Infinity);␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -509,6 +689,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = 1 - Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -530,6 +720,16 @@ Generated by [AVA](https://avajs.dev). 1 | const foo = 1 - -Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -551,6 +751,16 @@ Generated by [AVA](https://avajs.dev). 1 | const isPositiveZero = value => value === 0 && 1 / value === Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -572,6 +782,16 @@ Generated by [AVA](https://avajs.dev). 1 | const isNegativeZero = value => value === 0 && 1 / value === -Infinity;␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -733,6 +953,16 @@ Generated by [AVA](https://avajs.dev). 1 | function foo() {return-Infinity}␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ @@ -935,6 +1165,16 @@ Generated by [AVA](https://avajs.dev). 1 | -globalThis.Infinity␊ ` +> Options + + `␊ + [␊ + {␊ + "checkInfinity": true␊ + }␊ + ]␊ + ` + > Output `␊ diff --git a/test/snapshots/prefer-number-properties.mjs.snap b/test/snapshots/prefer-number-properties.mjs.snap index abd5cc5298..ad2f7b6233 100644 Binary files a/test/snapshots/prefer-number-properties.mjs.snap and b/test/snapshots/prefer-number-properties.mjs.snap differ diff --git a/test/snapshots/prefer-string-raw.mjs.md b/test/snapshots/prefer-string-raw.mjs.md new file mode 100644 index 0000000000..8a475cf888 --- /dev/null +++ b/test/snapshots/prefer-string-raw.mjs.md @@ -0,0 +1,89 @@ +# Snapshot report for `test/prefer-string-raw.mjs` + +The actual snapshot is saved in `prefer-string-raw.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): a = 'a\\b' + +> Input + + `␊ + 1 | a = 'a\\\\b'␊ + ` + +> Output + + `␊ + 1 | a = String.raw\`a\\b\`␊ + ` + +> Error 1/1 + + `␊ + > 1 | a = 'a\\\\b'␊ + | ^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.␊ + ` + +## invalid(2): a = {['a\\b']: b} + +> Input + + `␊ + 1 | a = {['a\\\\b']: b}␊ + ` + +> Output + + `␊ + 1 | a = {[String.raw\`a\\b\`]: b}␊ + ` + +> Error 1/1 + + `␊ + > 1 | a = {['a\\\\b']: b}␊ + | ^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.␊ + ` + +## invalid(3): function a() {return'a\\b'} + +> Input + + `␊ + 1 | function a() {return'a\\\\b'}␊ + ` + +> Output + + `␊ + 1 | function a() {return String.raw\`a\\b\`}␊ + ` + +> Error 1/1 + + `␊ + > 1 | function a() {return'a\\\\b'}␊ + | ^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.␊ + ` + +## invalid(4): const foo = "foo \\x46"; + +> Input + + `␊ + 1 | const foo = "foo \\\\x46";␊ + ` + +> Output + + `␊ + 1 | const foo = String.raw\`foo \\x46\`;␊ + ` + +> Error 1/1 + + `␊ + > 1 | const foo = "foo \\\\x46";␊ + | ^^^^^^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.␊ + ` diff --git a/test/snapshots/prefer-string-raw.mjs.snap b/test/snapshots/prefer-string-raw.mjs.snap new file mode 100644 index 0000000000..93f30083b5 Binary files /dev/null and b/test/snapshots/prefer-string-raw.mjs.snap differ diff --git a/test/snapshots/prefer-structured-clone.mjs.md b/test/snapshots/prefer-structured-clone.mjs.md new file mode 100644 index 0000000000..dfe6bee09c --- /dev/null +++ b/test/snapshots/prefer-structured-clone.mjs.md @@ -0,0 +1,301 @@ +# Snapshot report for `test/prefer-structured-clone.mjs` + +The actual snapshot is saved in `prefer-structured-clone.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): JSON.parse(JSON.stringify(foo)) + +> Input + + `␊ + 1 | JSON.parse(JSON.stringify(foo))␊ + ` + +> Error 1/1 + + `␊ + > 1 | JSON.parse(JSON.stringify(foo))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo)␊ + ` + +## invalid(2): JSON.parse(JSON.stringify(foo),) + +> Input + + `␊ + 1 | JSON.parse(JSON.stringify(foo),)␊ + ` + +> Error 1/1 + + `␊ + > 1 | JSON.parse(JSON.stringify(foo),)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo,)␊ + ` + +## invalid(3): JSON.parse(JSON.stringify(foo,)) + +> Input + + `␊ + 1 | JSON.parse(JSON.stringify(foo,))␊ + ` + +> Error 1/1 + + `␊ + > 1 | JSON.parse(JSON.stringify(foo,))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo)␊ + ` + +## invalid(4): JSON.parse(JSON.stringify(foo,),) + +> Input + + `␊ + 1 | JSON.parse(JSON.stringify(foo,),)␊ + ` + +> Error 1/1 + + `␊ + > 1 | JSON.parse(JSON.stringify(foo,),)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo,)␊ + ` + +## invalid(5): JSON.parse( ((JSON.stringify)) (foo)) + +> Input + + `␊ + 1 | JSON.parse( ((JSON.stringify)) (foo))␊ + ` + +> Error 1/1 + + `␊ + > 1 | JSON.parse( ((JSON.stringify)) (foo))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone( foo)␊ + ` + +## invalid(6): (( JSON.parse)) (JSON.stringify(foo)) + +> Input + + `␊ + 1 | (( JSON.parse)) (JSON.stringify(foo))␊ + ` + +> Error 1/1 + + `␊ + > 1 | (( JSON.parse)) (JSON.stringify(foo))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | (( structuredClone)) (foo)␊ + ` + +## invalid(7): JSON.parse(JSON.stringify( ((foo)) )) + +> Input + + `␊ + 1 | JSON.parse(JSON.stringify( ((foo)) ))␊ + ` + +> Error 1/1 + + `␊ + > 1 | JSON.parse(JSON.stringify( ((foo)) ))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone( ((foo)) )␊ + ` + +## invalid(8): function foo() { return JSON .parse( JSON. stringify( bar, ), ); } + +> Input + + `␊ + 1 | function foo() {␊ + 2 | return JSON␊ + 3 | .parse(␊ + 4 | JSON.␊ + 5 | stringify(␊ + 6 | bar,␊ + 7 | ),␊ + 8 | );␊ + 9 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | return JSON␊ + | ^^^^␊ + > 3 | .parse(␊ + | ^^^^^^^^^␊ + > 4 | JSON.␊ + | ^^^^^^^^^␊ + > 5 | stringify(␊ + | ^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`JSON.parse(JSON.stringify(…))\` to create a deep clone.␊ + 6 | bar,␊ + 7 | ),␊ + 8 | );␊ + 9 | }␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | function foo() {␊ + 2 | return structuredClone(␊ + 3 | ␊ + 4 | bar␊ + 5 | ,␊ + 6 | );␊ + 7 | }␊ + ` + +## invalid(1): _.cloneDeep(foo) + +> Input + + `␊ + 1 | _.cloneDeep(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | _.cloneDeep(foo)␊ + | ^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`_.cloneDeep(…)\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo)␊ + ` + +## invalid(2): lodash.cloneDeep(foo) + +> Input + + `␊ + 1 | lodash.cloneDeep(foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | lodash.cloneDeep(foo)␊ + | ^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`lodash.cloneDeep(…)\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo)␊ + ` + +## invalid(3): lodash.cloneDeep(foo,) + +> Input + + `␊ + 1 | lodash.cloneDeep(foo,)␊ + ` + +> Error 1/1 + + `␊ + > 1 | lodash.cloneDeep(foo,)␊ + | ^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`lodash.cloneDeep(…)\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo,)␊ + ` + +## invalid(4): myCustomDeepCloneFunction(foo,) + +> Input + + `␊ + 1 | myCustomDeepCloneFunction(foo,)␊ + ` + +> Options + + `␊ + [␊ + {␊ + "functions": [␊ + "myCustomDeepCloneFunction"␊ + ]␊ + }␊ + ]␊ + ` + +> Error 1/1 + + `␊ + > 1 | myCustomDeepCloneFunction(foo,)␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`myCustomDeepCloneFunction(…)\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo,)␊ + ` + +## invalid(5): my.cloneDeep(foo,) + +> Input + + `␊ + 1 | my.cloneDeep(foo,)␊ + ` + +> Options + + `␊ + [␊ + {␊ + "functions": [␊ + "my.cloneDeep"␊ + ]␊ + }␊ + ]␊ + ` + +> Error 1/1 + + `␊ + > 1 | my.cloneDeep(foo,)␊ + | ^^^^^^^^^^^^ Prefer \`structuredClone(…)\` over \`my.cloneDeep(…)\` to create a deep clone.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Switch to \`structuredClone(…)\`.␊ + 1 | structuredClone(foo,)␊ + ` diff --git a/test/snapshots/prefer-structured-clone.mjs.snap b/test/snapshots/prefer-structured-clone.mjs.snap new file mode 100644 index 0000000000..5e3bfab094 Binary files /dev/null and b/test/snapshots/prefer-structured-clone.mjs.snap differ diff --git a/test/snapshots/throw-new-error.mjs.md b/test/snapshots/throw-new-error.mjs.md new file mode 100644 index 0000000000..903b034a02 --- /dev/null +++ b/test/snapshots/throw-new-error.mjs.md @@ -0,0 +1,563 @@ +# Snapshot report for `test/throw-new-error.mjs` + +The actual snapshot is saved in `throw-new-error.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): throw Error() + +> Input + + `␊ + 1 | throw Error()␊ + ` + +> Output + + `␊ + 1 | throw new Error()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw Error()␊ + | ^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(2): throw (Error)() + +> Input + + `␊ + 1 | throw (Error)()␊ + ` + +> Output + + `␊ + 1 | throw new (Error)()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw (Error)()␊ + | ^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(3): throw lib.Error() + +> Input + + `␊ + 1 | throw lib.Error()␊ + ` + +> Output + + `␊ + 1 | throw new lib.Error()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw lib.Error()␊ + | ^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(4): throw lib.mod.Error() + +> Input + + `␊ + 1 | throw lib.mod.Error()␊ + ` + +> Output + + `␊ + 1 | throw new lib.mod.Error()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw lib.mod.Error()␊ + | ^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(5): throw lib[mod].Error() + +> Input + + `␊ + 1 | throw lib[mod].Error()␊ + ` + +> Output + + `␊ + 1 | throw new lib[mod].Error()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw lib[mod].Error()␊ + | ^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(6): throw (lib.mod).Error() + +> Input + + `␊ + 1 | throw (lib.mod).Error()␊ + ` + +> Output + + `␊ + 1 | throw new (lib.mod).Error()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw (lib.mod).Error()␊ + | ^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(7): throw Error('foo') + +> Input + + `␊ + 1 | throw Error('foo')␊ + ` + +> Output + + `␊ + 1 | throw new Error('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw Error('foo')␊ + | ^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(8): throw CustomError('foo') + +> Input + + `␊ + 1 | throw CustomError('foo')␊ + ` + +> Output + + `␊ + 1 | throw new CustomError('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw CustomError('foo')␊ + | ^^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(9): throw FooBarBazError('foo') + +> Input + + `␊ + 1 | throw FooBarBazError('foo')␊ + ` + +> Output + + `␊ + 1 | throw new FooBarBazError('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw FooBarBazError('foo')␊ + | ^^^^^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(10): throw ABCError('foo') + +> Input + + `␊ + 1 | throw ABCError('foo')␊ + ` + +> Output + + `␊ + 1 | throw new ABCError('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw ABCError('foo')␊ + | ^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(11): throw Abc3Error('foo') + +> Input + + `␊ + 1 | throw Abc3Error('foo')␊ + ` + +> Output + + `␊ + 1 | throw new Abc3Error('foo')␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw Abc3Error('foo')␊ + | ^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(12): throw TypeError() + +> Input + + `␊ + 1 | throw TypeError()␊ + ` + +> Output + + `␊ + 1 | throw new TypeError()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw TypeError()␊ + | ^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(13): throw EvalError() + +> Input + + `␊ + 1 | throw EvalError()␊ + ` + +> Output + + `␊ + 1 | throw new EvalError()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw EvalError()␊ + | ^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(14): throw RangeError() + +> Input + + `␊ + 1 | throw RangeError()␊ + ` + +> Output + + `␊ + 1 | throw new RangeError()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw RangeError()␊ + | ^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(15): throw ReferenceError() + +> Input + + `␊ + 1 | throw ReferenceError()␊ + ` + +> Output + + `␊ + 1 | throw new ReferenceError()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw ReferenceError()␊ + | ^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(16): throw SyntaxError() + +> Input + + `␊ + 1 | throw SyntaxError()␊ + ` + +> Output + + `␊ + 1 | throw new SyntaxError()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw SyntaxError()␊ + | ^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(17): throw URIError() + +> Input + + `␊ + 1 | throw URIError()␊ + ` + +> Output + + `␊ + 1 | throw new URIError()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw URIError()␊ + | ^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(18): throw (( URIError() )) + +> Input + + `␊ + 1 | throw (( URIError() ))␊ + ` + +> Output + + `␊ + 1 | throw (( new URIError() ))␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw (( URIError() ))␊ + | ^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(19): throw (( URIError ))() + +> Input + + `␊ + 1 | throw (( URIError ))()␊ + ` + +> Output + + `␊ + 1 | throw new (( URIError ))()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw (( URIError ))()␊ + | ^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(20): throw getGlobalThis().Error() + +> Input + + `␊ + 1 | throw getGlobalThis().Error()␊ + ` + +> Output + + `␊ + 1 | throw new (getGlobalThis().Error)()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw getGlobalThis().Error()␊ + | ^^^^^^^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(21): throw utils.getGlobalThis().Error() + +> Input + + `␊ + 1 | throw utils.getGlobalThis().Error()␊ + ` + +> Output + + `␊ + 1 | throw new (utils.getGlobalThis().Error)()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw utils.getGlobalThis().Error()␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(22): throw (( getGlobalThis().Error ))() + +> Input + + `␊ + 1 | throw (( getGlobalThis().Error ))()␊ + ` + +> Output + + `␊ + 1 | throw new (( getGlobalThis().Error ))()␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw (( getGlobalThis().Error ))()␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(23): const error = Error() + +> Input + + `␊ + 1 | const error = Error()␊ + ` + +> Output + + `␊ + 1 | const error = new Error()␊ + ` + +> Error 1/1 + + `␊ + > 1 | const error = Error()␊ + | ^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(24): throw Object.assign(Error(), {foo}) + +> Input + + `␊ + 1 | throw Object.assign(Error(), {foo})␊ + ` + +> Output + + `␊ + 1 | throw Object.assign(new Error(), {foo})␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw Object.assign(Error(), {foo})␊ + | ^^^^^^^ Use \`new\` when creating an error.␊ + ` + +## invalid(25): new Promise((resolve, reject) => { reject(Error('message')); }); + +> Input + + `␊ + 1 | new Promise((resolve, reject) => {␊ + 2 | reject(Error('message'));␊ + 3 | });␊ + ` + +> Output + + `␊ + 1 | new Promise((resolve, reject) => {␊ + 2 | reject(new Error('message'));␊ + 3 | });␊ + ` + +> Error 1/1 + + `␊ + 1 | new Promise((resolve, reject) => {␊ + > 2 | reject(Error('message'));␊ + | ^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + 3 | });␊ + ` + +## invalid(26): function foo() { return[globalThis][0].Error('message'); } + +> Input + + `␊ + 1 | function foo() {␊ + 2 | return[globalThis][0].Error('message');␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | return new [globalThis][0].Error('message');␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | return[globalThis][0].Error('message');␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use \`new\` when creating an error.␊ + 3 | }␊ + ` diff --git a/test/snapshots/throw-new-error.mjs.snap b/test/snapshots/throw-new-error.mjs.snap new file mode 100644 index 0000000000..84e6045ed9 Binary files /dev/null and b/test/snapshots/throw-new-error.mjs.snap differ diff --git a/test/string-content.mjs b/test/string-content.mjs index c52f33fa0e..b264182e8a 100644 --- a/test/string-content.mjs +++ b/test/string-content.mjs @@ -37,8 +37,8 @@ const createSuggestionError = (match, suggest, output) => [ data: { match, suggest, - output, }, + output, }, ], }, @@ -46,9 +46,11 @@ const createSuggestionError = (match, suggest, output) => [ test({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, @@ -111,26 +113,26 @@ test({ // Escape single quote { code: 'const foo = \'quote\'', - output: 'const foo = \'\\\'"\'', + output: String.raw`const foo = '\'"'`, options: [{patterns}], errors: createError('quote', '\'"'), }, { - code: 'const foo = \'\\\\quote\\\\\'', - output: 'const foo = \'\\\\\\\'"\\\\\'', + code: String.raw`const foo = '\\quote\\'`, + output: String.raw`const foo = '\\\'"\\'`, options: [{patterns}], errors: createError('quote', '\'"'), }, // Escape double quote { code: 'const foo = "quote"', - output: 'const foo = "\'\\""', + output: String.raw`const foo = "'\""`, options: [{patterns}], errors: createError('quote', '\'"'), }, { - code: 'const foo = "\\\\quote\\\\"', - output: 'const foo = "\\\\\'\\"\\\\"', + code: String.raw`const foo = "\\quote\\"`, + output: String.raw`const foo = "\\'\"\\"`, options: [{patterns}], errors: createError('quote', '\'"'), }, @@ -181,7 +183,7 @@ test({ code: 'const foo = "foo.bar"', output: 'const foo = "foo_bar"', options: [{patterns: {'\\.': '_'}}], // <- escaped - errors: createError('\\.', '_'), + errors: createError(String.raw`\.`, '_'), }, // Custom message @@ -195,15 +197,15 @@ test({ // Should not crash on multiline string // https://github.com/avajs/ava/blob/7f99aef61f3aed2389ca9407115ad4c9aecada92/test/assert.js#L1477 { - code: 'const foo = "no\\n"', - output: 'const foo = "yes\\n"', + code: String.raw`const foo = "no\n"`, + output: String.raw`const foo = "yes\n"`, options: [{patterns: noToYesPattern}], errors: createError('no', 'yes'), }, // https://github.com/sindresorhus/execa/blob/df08cfb2d849adb31dc764ca3ab5f29e5b191d50/test/error.js#L20 { - code: 'const foo = "no\\r"', - output: 'const foo = "yes\\r"', + code: String.raw`const foo = "no\r"`, + output: String.raw`const foo = "yes\r"`, options: [{patterns: noToYesPattern}], errors: createError('no', 'yes'), }, diff --git a/test/template-indent.mjs b/test/template-indent.mjs index 55a377ab6a..eacc8b196e 100644 --- a/test/template-indent.mjs +++ b/test/template-indent.mjs @@ -85,6 +85,26 @@ test({ β€’β€’β€’β€’β€’β€’β€’β€’\` `), }, + { + options: [{ + tags: ['utils.dedent'], + }], + code: fixInput(` + foo = utils.dedent\` + β€’β€’β€’β€’β€’β€’β€’β€’one + β€’β€’β€’β€’β€’β€’β€’β€’two + β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’three + β€’β€’β€’β€’β€’β€’β€’β€’\` + `), + errors, + output: fixInput(` + foo = utils.dedent\` + β€’β€’one + β€’β€’two + β€’β€’β€’β€’three + \` + `), + }, { options: [{ tags: ['customIndentableTag'], @@ -412,15 +432,15 @@ test({ }, { options: [{ - functions: ['customDedentFunction'], + functions: ['customDedentFunction1', 'utils.customDedentFunction2'], }], code: fixInput(` - foo = customDedentFunction(\` + foo = customDedentFunction1(\` β€’β€’β€’β€’β€’β€’β€’β€’one β€’β€’β€’β€’β€’β€’β€’β€’two β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’three β€’β€’β€’β€’β€’β€’β€’β€’\`) - foo = customDedentFunction('some-other-arg', \` + foo = utils.customDedentFunction2('some-other-arg', \` β€’β€’β€’β€’β€’β€’β€’β€’one β€’β€’β€’β€’β€’β€’β€’β€’two β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’three @@ -428,12 +448,12 @@ test({ `), errors: [...errors, ...errors], output: fixInput(` - foo = customDedentFunction(\` + foo = customDedentFunction1(\` β€’β€’one β€’β€’two β€’β€’β€’β€’three \`) - foo = customDedentFunction('some-other-arg', \` + foo = utils.customDedentFunction2('some-other-arg', \` β€’β€’one β€’β€’two β€’β€’β€’β€’three diff --git a/test/text-encoding-identifier-case.mjs b/test/text-encoding-identifier-case.mjs index a3cd946bce..6bee90f2d1 100644 --- a/test/text-encoding-identifier-case.mjs +++ b/test/text-encoding-identifier-case.mjs @@ -9,7 +9,7 @@ test.snapshot({ '"utf+8"', '" utf8 "', '\'utf8\'', - '"\\u0055tf8"', + String.raw`"\u0055tf8"`, 'const ASCII = 1', 'const UTF8 = 1', ], @@ -39,9 +39,11 @@ test.snapshot({ // JSX test.snapshot({ testerOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, }, diff --git a/test/throw-new-error.mjs b/test/throw-new-error.mjs index 882a5f44ee..7019c57ca4 100644 --- a/test/throw-new-error.mjs +++ b/test/throw-new-error.mjs @@ -1,11 +1,9 @@ +import {outdent} from 'outdent'; import {getTester} from './utils/test.mjs'; const {test} = getTester(import.meta); -const messageId = 'throw-new-error'; -const errors = [{messageId}]; - -test({ +test.snapshot({ valid: [ 'throw new Error()', 'new Error()', @@ -33,115 +31,39 @@ test({ 'throw lib.getError()', ], invalid: [ - { - code: 'throw Error()', - output: 'throw new Error()', - errors, - }, - { - code: 'throw (Error)()', - output: 'throw new (Error)()', - errors, - }, - { - code: 'throw lib.Error()', - output: 'throw new lib.Error()', - errors, - }, - { - code: 'throw lib.mod.Error()', - output: 'throw new lib.mod.Error()', - errors, - }, - { - code: 'throw lib[mod].Error()', - output: 'throw new lib[mod].Error()', - errors, - }, - { - code: 'throw (lib.mod).Error()', - output: 'throw new (lib.mod).Error()', - errors, - }, - { - code: 'throw Error(\'foo\')', - output: 'throw new Error(\'foo\')', - errors, - }, - { - code: 'throw CustomError(\'foo\')', - output: 'throw new CustomError(\'foo\')', - errors, - }, - { - code: 'throw FooBarBazError(\'foo\')', - output: 'throw new FooBarBazError(\'foo\')', - errors, - }, - { - code: 'throw ABCError(\'foo\')', - output: 'throw new ABCError(\'foo\')', - errors, - }, - { - code: 'throw Abc3Error(\'foo\')', - output: 'throw new Abc3Error(\'foo\')', - errors, - }, - { - code: 'throw TypeError()', - output: 'throw new TypeError()', - errors, - }, - { - code: 'throw EvalError()', - output: 'throw new EvalError()', - errors, - }, - { - code: 'throw RangeError()', - output: 'throw new RangeError()', - errors, - }, - { - code: 'throw ReferenceError()', - output: 'throw new ReferenceError()', - errors, - }, - { - code: 'throw SyntaxError()', - output: 'throw new SyntaxError()', - errors, - }, - { - code: 'throw URIError()', - output: 'throw new URIError()', - errors, - }, - { - code: 'throw (( URIError() ))', - output: 'throw (( new URIError() ))', - errors, - }, - { - code: 'throw (( URIError ))()', - output: 'throw new (( URIError ))()', - errors, - }, - { - code: 'throw getGlobalThis().Error()', - output: 'throw new (getGlobalThis().Error)()', - errors, - }, - { - code: 'throw utils.getGlobalThis().Error()', - output: 'throw new (utils.getGlobalThis().Error)()', - errors, - }, - { - code: 'throw (( getGlobalThis().Error ))()', - output: 'throw new (( getGlobalThis().Error ))()', - errors, - }, + 'throw Error()', + 'throw (Error)()', + 'throw lib.Error()', + 'throw lib.mod.Error()', + 'throw lib[mod].Error()', + 'throw (lib.mod).Error()', + 'throw Error(\'foo\')', + 'throw CustomError(\'foo\')', + 'throw FooBarBazError(\'foo\')', + 'throw ABCError(\'foo\')', + 'throw Abc3Error(\'foo\')', + 'throw TypeError()', + 'throw EvalError()', + 'throw RangeError()', + 'throw ReferenceError()', + 'throw SyntaxError()', + 'throw URIError()', + 'throw (( URIError() ))', + 'throw (( URIError ))()', + 'throw getGlobalThis().Error()', + 'throw utils.getGlobalThis().Error()', + 'throw (( getGlobalThis().Error ))()', + 'const error = Error()', + 'throw Object.assign(Error(), {foo})', + outdent` + new Promise((resolve, reject) => { + reject(Error('message')); + }); + `, + outdent` + function foo() { + return[globalThis][0].Error('message'); + } + `, ], }); diff --git a/test/utils/default-options.mjs b/test/utils/default-options.mjs index abff0e52e0..acfd2144b7 100644 --- a/test/utils/default-options.mjs +++ b/test/utils/default-options.mjs @@ -1,14 +1,7 @@ -import eslintPluginUnicorn from '../../index.js'; - -const {env, parserOptions} = eslintPluginUnicorn.configs.recommended; - const defaultOptions = { - env: { - node: true, - browser: true, - ...env, + languageOptions: { + sourceType: 'module', }, - parserOptions, }; export default defaultOptions; diff --git a/test/utils/language-options.mjs b/test/utils/language-options.mjs new file mode 100644 index 0000000000..dca65a3a5e --- /dev/null +++ b/test/utils/language-options.mjs @@ -0,0 +1,61 @@ +import {Legacy} from '@eslint/eslintrc'; +import * as espree from 'espree'; + +const DEFAULT_LANGUAGE_OPTIONS = { + // When `parser` in `undefined`, `languageOptions` seems has no effect + parser: espree, + globals: Object.fromEntries( + ['es2024', 'node', 'browser'] + .flatMap(environment => Object.entries(Legacy.environments.get(environment).globals)), + ), +}; + +function cleanLanguageOptions(languageOptions) { + if (!languageOptions.parser) { + delete languageOptions.parser; + } + + if (!languageOptions.parserOptions) { + delete languageOptions.parserOptions; + } + + return languageOptions; +} + +function normalizeLanguageOptions(languageOptions) { + languageOptions ??= {}; + + const {parser, parserOptions} = languageOptions; + + const { + implementation: parserImplementation, + mergeParserOptions, + } = parser ?? {}; + + return cleanLanguageOptions({ + ...languageOptions, + parser: parserImplementation ?? parser, + parserOptions: mergeParserOptions?.(parserOptions) ?? parserOptions, + }); +} + +function mergeLanguageOptions(languageOptionsA, languageOptionsB) { + languageOptionsA ??= {}; + languageOptionsB ??= {}; + + return normalizeLanguageOptions({ + ...languageOptionsA, + ...languageOptionsB, + parser: languageOptionsB.parser ?? languageOptionsA.parser, + globals: { + ...languageOptionsA.globals, + ...languageOptionsB.globals, + }, + parserOptions: { + ...languageOptionsA.parserOptions, + ...languageOptionsB.parserOptions, + }, + }); +} + +export {DEFAULT_LANGUAGE_OPTIONS, normalizeLanguageOptions, mergeLanguageOptions}; diff --git a/test/utils/parsers.mjs b/test/utils/parsers.mjs index 6f1e4cb959..59e4d43bfa 100644 --- a/test/utils/parsers.mjs +++ b/test/utils/parsers.mjs @@ -1,17 +1,14 @@ -import {createRequire} from 'node:module'; -import defaultOptions from './default-options.mjs'; +import babelEslintParser from '@babel/eslint-parser'; +import typescriptEslintParser from '@typescript-eslint/parser'; +import vueEslintParser from 'vue-eslint-parser'; -const require = createRequire(import.meta.url); - -const babel = { - name: '@babel/eslint-parser', - get parser() { - return require.resolve(this.name); - }, +const babelParser = { + name: 'babel', + implementation: babelEslintParser, mergeParserOptions(options) { - options = options || {}; - options.babelOptions = options.babelOptions || {}; - options.babelOptions.parserOpts = options.babelOptions.parserOpts || {}; + options ??= {}; + options.babelOptions ??= {}; + options.babelOptions.parserOpts ??= {}; let babelPlugins = options.babelOptions.parserOpts.plugins || []; babelPlugins = [ @@ -22,9 +19,7 @@ const babel = { ]; return { - ...defaultOptions.parserOptions, requireConfigFile: false, - sourceType: 'module', allowImportExportEverywhere: true, ...options, babelOptions: { @@ -40,37 +35,28 @@ const babel = { }, }; -const typescript = { - name: '@typescript-eslint/parser', - get parser() { - return require.resolve(this.name); - }, +const typescriptParser = { + name: 'typescript', + implementation: typescriptEslintParser, mergeParserOptions(options) { return { - ...defaultOptions.parserOptions, project: [], ...options, }; }, }; -const vue = { - name: 'vue-eslint-parser', - get parser() { - return require.resolve(this.name); - }, - mergeParserOptions(options) { - return { - ...defaultOptions.parserOptions, - ...options, - }; - }, +const vueParser = { + name: 'vue', + implementation: vueEslintParser, }; -const parsers = { - babel, - typescript, - vue, -}; +const parsers = Object.fromEntries( + [ + babelParser, + typescriptParser, + vueParser, + ].map(parser => [parser.name, parser]), +); export default parsers; diff --git a/test/utils/snapshot-rule-tester.mjs b/test/utils/snapshot-rule-tester.mjs index 46cc903095..2847be3f14 100644 --- a/test/utils/snapshot-rule-tester.mjs +++ b/test/utils/snapshot-rule-tester.mjs @@ -1,9 +1,9 @@ -import {createRequire} from 'node:module'; +import path from 'node:path'; import {Linter} from 'eslint'; import {codeFrameColumns} from '@babel/code-frame'; import outdent from 'outdent'; +import {mergeLanguageOptions} from './language-options.mjs'; -const require = createRequire(import.meta.url); const codeFrameColumnsOptions = {linesAbove: Number.POSITIVE_INFINITY, linesBelow: Number.POSITIVE_INFINITY}; // A simple version of `SourceCodeFixer.applyFixes` // https://github.com/eslint/eslint/issues/14936#issuecomment-906746754 @@ -60,7 +60,7 @@ function normalizeTests(tests) { const additionalProperties = getAdditionalProperties( testCase, - ['code', 'options', 'filename', 'parserOptions', 'parser', 'globals', 'only'], + ['code', 'options', 'filename', 'languageOptions', 'only'], ); if (additionalProperties.length > 0) { @@ -72,56 +72,47 @@ function normalizeTests(tests) { return tests; } -function getVerifyConfig(ruleId, testerConfig, testCase) { +function getVerifyConfig(ruleId, rule, testerConfig, testCase) { const { - options, - parserOptions, - parser = testerConfig.parser, - env, - globals, + languageOptions = {}, + options = [], } = testCase; - return { - ...testerConfig, - parser, - parserOptions: { - ...testerConfig.parserOptions, - ...parserOptions, - }, - env: { - ...testerConfig.env, - ...env, - }, - globals: { - ...testerConfig.globals, - ...globals, - }, - rules: { - [ruleId]: ['error', ...(Array.isArray(options) ? options : [])], + // https://github.com/eslint/eslint/blob/ee7f9e62102d3dd0b7581d1e88e41bce3385980a/lib/rule-tester/rule-tester.js#L501 + const pluginName = 'rule-to-test'; + + return [ + // https://github.com/eslint/eslint/blob/ee7f9e62102d3dd0b7581d1e88e41bce3385980a/lib/rule-tester/rule-tester.js#L524 + {files: ['**']}, + { + ...testerConfig, + languageOptions: mergeLanguageOptions(testerConfig.languageOptions, languageOptions), + rules: { + [`${pluginName}/${ruleId}`]: ['error', ...options], + }, + plugins: { + [pluginName]: { + rules: { + [ruleId]: rule, + }, + }, + }, + // https://github.com/eslint/eslint/blob/ee7f9e62102d3dd0b7581d1e88e41bce3385980a/lib/config/default-config.js#L46-L48 + linterOptions: { + reportUnusedDisableDirectives: 'off', + }, }, - }; + ]; } -const parsers = new WeakMap(); -function defineParser(linter, parser) { - if (!parser) { - return; - } - - if (!parsers.has(linter)) { - parsers.set(linter, new Set()); - } - - const defined = parsers.get(linter); - if (defined.has(parser)) { - return; +function verify(code, verifyConfig, {filename}) { + // https://github.com/eslint/eslint/pull/17989 + const linterOptions = {}; + if (filename) { + linterOptions.cwd = path.parse(filename).root; } - defined.add(parser); - linter.defineParser(parser, require(parser)); -} - -function verify(linter, code, verifyConfig, {filename}) { + const linter = new Linter(linterOptions); const messages = linter.verify(code, verifyConfig, {filename}); // Missed `message`, #1923 @@ -136,32 +127,32 @@ function verify(linter, code, verifyConfig, {filename}) { throw new SyntaxError('\n' + codeFrameColumns(code, {start: {line, column}}, {message})); } - return messages; + return { + linter, + messages, + }; } class SnapshotRuleTester { - constructor(test, config) { + constructor(test, testerConfig) { this.test = test; - this.config = config; + this.testerConfig = testerConfig; } run(ruleId, rule, tests) { - const {test, config} = this; + const {test, testerConfig} = this; const fixable = rule.meta && rule.meta.fixable; - const linter = new Linter(); - linter.defineRule(ruleId, rule); const {valid, invalid} = normalizeTests(tests); for (const [index, testCase] of valid.entries()) { const {code, filename, only} = testCase; - const verifyConfig = getVerifyConfig(ruleId, config, testCase); - defineParser(linter, verifyConfig.parser); + const verifyConfig = getVerifyConfig(ruleId, rule, testerConfig, testCase); (only ? test.only : test)( `valid(${index + 1}): ${code}`, t => { - const messages = verify(linter, code, verifyConfig, {filename}); + const {messages} = verify(code, verifyConfig, {filename}); t.deepEqual(messages, [], 'Valid case should not have errors.'); }, ); @@ -169,16 +160,15 @@ class SnapshotRuleTester { for (const [index, testCase] of invalid.entries()) { const {code, options, filename, only} = testCase; - const verifyConfig = getVerifyConfig(ruleId, config, testCase); - defineParser(linter, verifyConfig.parser); - const runVerify = code => verify(linter, code, verifyConfig, {filename}); + const verifyConfig = getVerifyConfig(ruleId, rule, testerConfig, testCase); + const runVerify = code => verify(code, verifyConfig, {filename}); (only ? test.only : test)( `invalid(${index + 1}): ${code}`, t => { - const messages = runVerify(code); - t.notDeepEqual(messages, [], 'Invalid case should have at least one error.'); + const {linter, messages} = runVerify(code); + t.notDeepEqual(messages, [], 'Invalid case should have at least one error.'); const {fixed, output} = fixable ? linter.verifyAndFix(code, verifyConfig, {filename}) : {fixed: false}; t.snapshot(`\n${printCode(code)}\n`, 'Input'); diff --git a/test/utils/test.mjs b/test/utils/test.mjs index cf929647db..638a22536b 100644 --- a/test/utils/test.mjs +++ b/test/utils/test.mjs @@ -4,11 +4,19 @@ import test from 'ava'; import AvaRuleTester from 'eslint-ava-rule-tester'; import {loadRule} from '../../rules/utils/rule.js'; import SnapshotRuleTester from './snapshot-rule-tester.mjs'; -import defaultOptions from './default-options.mjs'; import parsers from './parsers.mjs'; +import {DEFAULT_LANGUAGE_OPTIONS, normalizeLanguageOptions, mergeLanguageOptions} from './language-options.mjs'; -function normalizeTestCase(testCase) { - return typeof testCase === 'string' ? {code: testCase} : {...testCase}; +function normalizeTestCase(testCase, shouldNormalizeLanguageOptions = true) { + if (typeof testCase === 'string') { + testCase = {code: testCase}; + } + + if (shouldNormalizeLanguageOptions && testCase.languageOptions) { + testCase = {...testCase, languageOptions: normalizeLanguageOptions(testCase.languageOptions)}; + } + + return testCase; } function normalizeInvalidTest(test, rule) { @@ -33,25 +41,6 @@ function normalizeInvalidTest(test, rule) { }; } -function normalizeParser(options) { - let { - parser, - parserOptions, - } = options; - - if (parser) { - if (parser.mergeParserOptions) { - parserOptions = parser.mergeParserOptions(parserOptions); - } - - if (parser.name) { - parser = parser.name; - } - } - - return {...options, parser, parserOptions}; -} - // https://github.com/tc39/proposal-array-is-template-object const isTemplateObject = value => Array.isArray(value?.raw); // https://github.com/tc39/proposal-string-cooked @@ -72,7 +61,7 @@ function only(...arguments_) { only('code'); only({code: 'code'}); */ - return {...normalizeTestCase(arguments_[0]), only: true}; + return {...normalizeTestCase(arguments_[0], /* shouldNormalizeLanguageOptions */ false), only: true}; } class Tester { @@ -82,64 +71,41 @@ class Tester { } runTest(tests) { - const {beforeAll, testerOptions = {}, valid, invalid} = tests; - const tester = new AvaRuleTester(test, { + const {ruleId, rule} = this; + + let {testerOptions = {}, valid, invalid} = tests; + + valid = valid.map(testCase => normalizeTestCase(testCase)); + invalid = invalid.map(testCase => normalizeInvalidTest(normalizeTestCase(testCase), rule)); + + const testConfig = { ...testerOptions, - parserOptions: { - ...defaultOptions.parserOptions, - ...testerOptions.parserOptions, - }, - env: { - ...defaultOptions.env, - ...testerOptions.env, - }, - globals: { - ...defaultOptions.globals, - ...testerOptions.globals, - }, - }); + languageOptions: mergeLanguageOptions(DEFAULT_LANGUAGE_OPTIONS, testerOptions.languageOptions), + }; - if (beforeAll) { - beforeAll(tester); - } + const tester = new AvaRuleTester(test, testConfig); return tester.run( - this.ruleId, - this.rule, - { - valid, - invalid: invalid.map(test => normalizeInvalidTest(test, this.rule)), - }, + ruleId, + rule, + {valid, invalid}, ); } snapshot(tests) { - let { - testerOptions = {}, - valid, - invalid, - } = tests; + let {testerOptions = {}, valid, invalid} = tests; - testerOptions = normalizeParser(testerOptions); - valid = valid.map(testCase => normalizeParser(normalizeTestCase(testCase))); - invalid = invalid.map(testCase => normalizeParser(normalizeTestCase(testCase))); + valid = valid.map(testCase => normalizeTestCase(testCase)); + invalid = invalid.map(testCase => normalizeTestCase(testCase)); - const tester = new SnapshotRuleTester(test, { + const testConfig = { ...testerOptions, - parserOptions: { - ...defaultOptions.parserOptions, - ...testerOptions.parserOptions, - }, - env: { - ...defaultOptions.env, - ...testerOptions.env, - }, - globals: { - ...defaultOptions.globals, - ...testerOptions.globals, - }, - }); - return tester.run(this.ruleId, this.rule, {valid, invalid}); + languageOptions: mergeLanguageOptions(DEFAULT_LANGUAGE_OPTIONS, testerOptions.languageOptions), + }; + + const tester = new SnapshotRuleTester(test, testConfig); + const {ruleId, rule} = this; + return tester.run(ruleId, rule, {valid, invalid}); } } @@ -147,22 +113,22 @@ function getTester(importMeta) { const filename = url.fileURLToPath(importMeta.url); const ruleId = path.basename(filename, '.mjs'); const tester = new Tester(ruleId); + const runTest = Tester.prototype.runTest.bind(tester); runTest.snapshot = Tester.prototype.snapshot.bind(tester); runTest.only = only; - for (const [parserName, parserSettings] of Object.entries(parsers)) { - Reflect.defineProperty(runTest, parserName, { + for (const parser of Object.values(parsers)) { + Reflect.defineProperty(runTest, parser.name, { value(tests) { - const testerOptions = tests.testerOptions || {}; - const {parser, mergeParserOptions} = parserSettings; - return runTest({ ...tests, testerOptions: { - ...testerOptions, - parser, - parserOptions: mergeParserOptions(testerOptions.parserOptions), + ...tests.testerOptions, + languageOptions: { + ...tests.testerOptions?.languageOptions, + parser, + }, }, }); }, @@ -177,13 +143,13 @@ function getTester(importMeta) { } const addComment = (testCase, comment) => { - testCase = normalizeTestCase(testCase); + testCase = normalizeTestCase(testCase, /* shouldNormalizeLanguageOptions */ false); const {code, output} = testCase; const fixedTest = { ...testCase, code: `${code}\n/* ${comment} */`, }; - if (Object.prototype.hasOwnProperty.call(fixedTest, 'output') && typeof output === 'string') { + if (Object.hasOwn(fixedTest, 'output') && typeof output === 'string') { fixedTest.output = `${output}\n/* ${comment} */`; }